items = []
container_uuid = if @proxied.is_a?(Container) then uuid else get(:container_uuid) end
if container_uuid
- cols = ContainerRequest.columns.map(&:name) - %w(id updated_at mounts secret_mounts)
+ cols = ContainerRequest.columns.map(&:name) - %w(id updated_at mounts secret_mounts runtime_token)
my_children = @child_proxies || ContainerRequest.select(cols).where(requesting_container_uuid: container_uuid).results if !my_children
my_child_containers = my_children.map(&:container_uuid).compact.uniq
grandchildren = {}
<%
container_uuid = if @object.is_a?(Container) then @object.uuid elsif @object.is_a?(ContainerRequest) then @object.container_uuid end
if container_uuid
- cols = ContainerRequest.columns.map(&:name) - %w(id updated_at mounts)
+ cols = ContainerRequest.columns.map(&:name) - %w(id updated_at mounts runtime_token)
reqs = ContainerRequest.select(cols).where(requesting_container_uuid: container_uuid).results
load_preloaded_objects(reqs)
- Upgrading and migrations:
- admin/upgrading.html.textile.liquid
- install/migrate-docker19.html.textile.liquid
+ - admin/upgrade-crunch2.html.textile.liquid
- Users and Groups:
- install/cheat_sheet.html.textile.liquid
- admin/activation.html.textile.liquid
--- /dev/null
+---
+layout: default
+navsection: admin
+title: Upgrading to Containers API
+...
+
+{% comment %}
+Copyright (C) The Arvados Authors. All rights reserved.
+
+SPDX-License-Identifier: CC-BY-SA-3.0
+{% endcomment %}
+
+The "containers" API is the recommended way to submit compute work to Arvados. It supersedes the "jobs" API, which is deprecated.
+
+h2. Benefits over the "jobs" API
+
+* Simpler and more robust execution with fewer points of failure
+* Automatic retry for containers that fail to run to completion due to infrastructure errors
+* Scales to thousands of simultaneous containers
+* Able to support alternate schedulers/dispatchers in addition to slurm
+* Improved logging, different streams logs/metrics stored in different files in the log collection
+* Records more upfront detail about the compute node, and additional metrics (such as available disk space over the course of the container run)
+* Better behavior when deciding whether to reuse past work -- pick the oldest container that matches the criteria
+* Can reuse running containers between workflows, cancelling a workflow will not cancel containers that are shared with other workflows
+* Supports setting time-to-live on intermediate output collections for automatic cleanup
+* Supports "secret" inputs, suitable for passwords or access tokens, which are hidden from the API responses and logs, and forgotten after use
+* Does not require "git" for dispatching work
+
+h2. Differences from the "jobs" API
+
+Containers cannot reuse jobs (but can reuse other containers)
+
+Uses the service "crunch-dispatch-slurm":{{site.baseurl}}/install/crunch2-slurm/install-dispatch.html instead of @crunch-dispatch.rb@
+
+Non-CWL Arvados "pipeline templates" are not supported with containers. Pipeline templates should be rewritten in CWL and registered as "Workflows".
+
+The containers APIs is incompatible with the jobs API, code which integrates with the "jobs" API must be updated to work with containers
+
+Containers have network access disabled by default
+
+The keep mount only exposes collections which are explicitly listed as inputs
+
+h2. Migrating to "containers" API
+
+Run your workflows using @arvados-cwl-runner --api=containers@ (only necessary if both the jobs and containers APIs are enabled, if the jobs API is disabled, it will use the containers API automatically)
+
+Register your workflows so they can be run from workbench using @arvados-cwl-runner --api=containers --create-workflow@
+
+Read "Migrating running CWL on jobs API to containers API":{{site.baseurl}}/user/cwl/cwl-style.html#migrate
+
+Use @arv:APIRequirement: {}@ in the @requirements@ section of your CWL file to enable network access for the container (see "Arvados CWL Extensions":{{site.baseurl}}/user/cwl/cwl-extensions.html)
+
+For examples on how to manage container requests with the Python SDK, see "Python cookbook":{{site.baseurl}}/sdk/python/cookbook.html
|container_uuid|string|The uuid of the container that satisfies this container_request. The system may return a preexisting Container that matches the container request criteria. See "Container reuse":#container_reuse for more details.|Container reuse is the default behavior, but may be disabled with @use_existing: false@ to always create a new container.|
|container_count_max|integer|Maximum number of containers to start, i.e., the maximum number of "attempts" to be made.||
|mounts|hash|Objects to attach to the container's filesystem and stdin/stdout.|See "Mount types":#mount_types for more details.|
+|secret_mounts|hash|Objects to attach to the container's filesystem. Only "json" or "text" mount types allowed.|Not returned in API responses. Reset to empty when state is "Complete" or "Cancelled".|
|runtime_constraints|hash|Restrict the container's access to compute resources and the outside world.|Required when in "Committed" state. e.g.,<pre><code>{
"ram":12000000000,
"vcpus":2,
|log_uuid|string|Log collection containing log messages provided by the scheduler and crunch processes.|Null if the container has not yet completed.|
|output_uuid|string|Output collection created when the container finished successfully.|Null if the container has failed or not yet completed.|
|filters|string|Additional constraints for satisfying the container_request, given in the same form as the filters parameter accepted by the container_requests.list API.|
+|runtime_token|string|A v2 token to be passed into the container itself, used to access Keep-backed mounts, etc. |Not returned in API responses. Reset to null when state is "Complete" or "Cancelled".|
+|runtime_user_uuid|string|The user permission that will be granted to this container.||
+|runtime_auth_scopes|array of string|The scopes associated with the auth token used to run this container.||
h2(#priority). Priority
h2(#container_reuse). Container reuse
-When a container request is "Committed", the system will try to find and reuse an existing Container with the same command, cwd, environment, output_path, container_image, mounts, and runtime_constraints being requested. (Hashes in the serialized fields environment, mounts and runtime_constraints are compared without regard to key order.)
+When a container request is "Committed", the system will try to find and reuse an existing Container with the same command, cwd, environment, output_path, container_image, mounts, secret_mounts, runtime_constraints, runtime_user_uuid, and runtime_auth_scopes being requested. (Hashes in the serialized fields environment, mounts and runtime_constraints use normalized key order.)
In order of preference, the system will use:
* The first matching container to have finished successfully (i.e., reached state "Complete" with an exit_code of 0) whose log and output collections are still available.
|command|array of strings|Command to execute.| Must be equal to a ContainerRequest's command in order to satisfy the ContainerRequest.|
|output_path|string|Path to a directory or file inside the container that should be preserved as this container's output when it finishes.|Must be equal to a ContainerRequest's output_path in order to satisfy the ContainerRequest.|
|mounts|hash|Must contain the same keys as the ContainerRequest being satisfied. Each value must be within the range of values described in the ContainerRequest at the time the Container is assigned to the ContainerRequest.|See "Mount types":#mount_types for more details.|
+|secret_mounts|hash|Must contain the same keys as the ContainerRequest being satisfied. Each value must be within the range of values described in the ContainerRequest at the time the Container is assigned to the ContainerRequest.|Not returned in API responses. Reset to empty when state is "Complete" or "Cancelled".|
|runtime_constraints|hash|Compute resources, and access to the outside world, that are / were available to the container.
Generally this will contain additional keys that are not present in any corresponding ContainerRequests: for example, even if no ContainerRequests specified constraints on the number of CPU cores, the number of cores actually used will be recorded here.|e.g.,
<pre><code>{
|progress|number|A number between 0.0 and 1.0 describing the fraction of work done.||
|priority|integer|Range 0-1000. Indicate scheduling order preference.|Currently assigned by the system as the max() of the priorities of all associated ContainerRequests. See "container request priority":container_requests.html#priority .|
|exit_code|integer|Process exit code.|Null if state!="Complete"|
-|auth_uuid|string|UUID of a token to be passed into the container itself, used to access Keep-backed mounts, etc.|Null if state∉{"Locked","Running"}|
+|auth_uuid|string|UUID of a token to be passed into the container itself, used to access Keep-backed mounts, etc. Automatically assigned.|Null if state∉{"Locked","Running"} or if @runtime_token@ was provided.|
|locked_by_uuid|string|UUID of a token, indicating which dispatch process changed state to Locked. If null, any token can be used to lock. If not null, only the indicated token can modify this container.|Null if state∉{"Locked","Running"}|
+|runtime_token|string|A v2 token to be passed into the container itself, used to access Keep-backed mounts, etc.|Not returned in API responses. Reset to null when state is "Complete" or "Cancelled".|
h2(#container_states). Container states
h2(#migrate). Migrating running CWL on jobs API to containers API
-* When migrating from jobs API (--api=jobs) (sometimes referred to as "crunch v1") to the containers API (--api=containers) ("crunch v2") there are a few differences in behavior:
-** The tool is limited to accessing only collections which are explicitly listed in the input, and further limited to only the subdirectories of collections listed in input. For example, given an explicit file input @/dir/subdir/file1.txt@, a tool will not be able to implicitly access the file @/dir/file2.txt@. Use @secondaryFiles@ or a @Directory@ input to describe trees of files.
-** Files listed in @InitialWorkDirRequirement@ appear in the output directory as normal files (not symlinks) but cannot be moved, renamed or deleted. These files will be added to the output collection but without any additional copies of the underlying data.
-** Tools are disallowed network access by default. Tools which require network access must include @arv:APIRequirement: {}@ in their @requirements@ section.
+When migrating from jobs API (--api=jobs) (sometimes referred to as "crunch v1") to the containers API (--api=containers) ("crunch v2") there are a few differences in behavior:
+
+* A tool may fail to find an input file that could be found when run under the jobs API. This is because tools are limited to accessing collections explicitly listed in the input, and further limited to those individual files or subdirectories that are listed. For example, given an explicit file input @/dir/subdir/file1.txt@, a tool will not be allowed to implicitly access a file in the parent directory @/dir/file2.txt@. Use @secondaryFiles@ or a @Directory@ for files that need to be grouped together.
+* A tool may fail when attempting to rename or delete a file in the output directory. This may happen because files listed in @InitialWorkDirRequirement@ appear in the output directory as normal files (not symlinks) but cannot be moved, renamed or deleted unless marked as "writable" in CWL. These files will be added to the output collection but without any additional copies of the underlying data.
+* A tool may fail when attempting to access the network. This may happen because, unlike the jobs API, under the containers API network access is disabled by default. Tools which require network access should add @arv:APIRequirement: {}@ to the @requirements@ section.
if @object.locked_by_uuid != Thread.current[:api_client_authorization].uuid
raise ArvadosModel::PermissionDeniedError.new("Not locked by your token")
end
- @object = @object.auth
+ if @object.runtime_token.nil?
+ @object = @object.auth
+ else
+ @object = ApiClientAuthorization.validate(token: @object.runtime_token)
+ if @object.nil?
+ raise ArvadosModel::PermissionDeniedError.new("Invalid runtime_token")
+ end
+ end
show
end
if Thread.current[:api_client_authorization].nil?
send_error("Not logged in", status: 401)
else
- c = Container.where(auth_uuid: Thread.current[:api_client_authorization].uuid).first
- if c.nil?
+ @object = Container.for_current_token
+ if @object.nil?
send_error("Token is not associated with a container.", status: 404)
else
- @object = c
show
end
end
end
def secret_mounts
- if @object &&
- @object.auth_uuid &&
- @object.auth_uuid == Thread.current[:api_client_authorization].uuid
+ c = Container.for_current_token
+ if @object && c && @object.uuid == c.uuid
send_json({"secret_mounts" => @object.secret_mounts})
else
send_error("Token is not associated with this container.", status: 403)
case token[0..2]
when 'v2/'
- _, uuid, secret = token.split('/')
+ _, uuid, secret, optional = token.split('/')
unless uuid.andand.length == 27 && secret.andand.length.andand > 0
return nil
end
+ if !optional.nil?
+ # if "optional" is a container uuid, check that it
+ # matches expections.
+ c = Container.where(uuid: optional).first
+ if !c.nil?
+ if !c.auth_uuid.nil? and c.auth_uuid != uuid
+ # token doesn't match the container's token
+ return nil
+ end
+ if !c.runtime_token.nil? and "v2/#{uuid}/#{secret}" != c.runtime_token
+ # token doesn't match the container's token
+ return nil
+ end
+ if ![Container::Locked, Container::Running].include?(c.state)
+ # container isn't locked or running, token shouldn't be used
+ return nil
+ end
+ end
+ end
+
auth = ApiClientAuthorization.
includes(:user, :api_client).
where('uuid=? and (expires_at is null or expires_at > CURRENT_TIMESTAMP)', uuid).
exclude_trashed_records = "AND #{sql_table}.is_trashed = false"
end
- exclude_old_versions = ""
- if !include_old_versions && sql_table == "collections"
- exclude_old_versions = "AND #{sql_table}.uuid = #{sql_table}.current_version_uuid"
- end
-
if users_list.select { |u| u.is_admin }.any?
# Admin skips most permission checks, but still want to filter on trashed items.
if !include_trash
# Only include records where the owner is not trashed
sql_conds = "NOT EXISTS(SELECT 1 FROM #{PERMISSION_VIEW} "+
"WHERE trashed = 1 AND "+
- "(#{sql_table}.owner_uuid = target_uuid)) #{exclude_trashed_records} #{exclude_old_versions}"
+ "(#{sql_table}.owner_uuid = target_uuid)) #{exclude_trashed_records}"
end
end
else
"(#{sql_table}.head_uuid IN (:user_uuids) OR #{sql_table}.tail_uuid IN (:user_uuids)))"
end
- sql_conds = "(#{direct_check} #{owner_check} #{links_cond}) #{exclude_trashed_records} #{exclude_old_versions}"
+ sql_conds = "(#{direct_check} #{owner_check} #{links_cond}) #{exclude_trashed_records}"
+
+ end
+ if !include_old_versions && sql_table == "collections"
+ exclude_old_versions = "#{sql_table}.uuid = #{sql_table}.current_version_uuid"
+ if sql_conds.nil?
+ sql_conds = exclude_old_versions
+ else
+ sql_conds += " AND #{exclude_old_versions}"
+ end
end
self.where(sql_conds,
after_validation :assign_auth
before_save :sort_serialized_attrs
before_save :update_secret_mounts_md5
- before_save :scrub_secret_mounts
+ before_save :scrub_secrets
before_save :clear_runtime_status_when_queued
after_save :update_cr_logs
after_save :handle_completed
t.add :state
t.add :auth_uuid
t.add :scheduling_parameters
+ t.add :runtime_user_uuid
+ t.add :runtime_auth_scopes
end
# Supported states for a container
end
def self.full_text_searchable_columns
- super - ["secret_mounts", "secret_mounts_md5"]
+ super - ["secret_mounts", "secret_mounts_md5", "runtime_token"]
end
def self.searchable_columns *args
- super - ["secret_mounts_md5"]
+ super - ["secret_mounts_md5", "runtime_token"]
end
def logged_attributes
- super.except('secret_mounts')
+ super.except('secret_mounts', 'runtime_token')
end
def state_transitions
# Create a new container (or find an existing one) to satisfy the
# given container request.
def self.resolve(req)
- c_attrs = {
- command: req.command,
- cwd: req.cwd,
- environment: req.environment,
- output_path: req.output_path,
- container_image: resolve_container_image(req.container_image),
- mounts: resolve_mounts(req.mounts),
- runtime_constraints: resolve_runtime_constraints(req.runtime_constraints),
- scheduling_parameters: req.scheduling_parameters,
- secret_mounts: req.secret_mounts,
- }
+ if req.runtime_token.nil?
+ runtime_user = if req.modified_by_user_uuid.nil?
+ current_user
+ else
+ User.find_by_uuid(req.modified_by_user_uuid)
+ end
+ runtime_auth_scopes = ["all"]
+ else
+ auth = ApiClientAuthorization.validate(token: req.runtime_token)
+ if auth.nil?
+ raise ArgumentError.new "Invalid runtime token"
+ end
+ runtime_user = User.find_by_id(auth.user_id)
+ runtime_auth_scopes = auth.scopes
+ end
+ c_attrs = act_as_user runtime_user do
+ {
+ command: req.command,
+ cwd: req.cwd,
+ environment: req.environment,
+ output_path: req.output_path,
+ container_image: resolve_container_image(req.container_image),
+ mounts: resolve_mounts(req.mounts),
+ runtime_constraints: resolve_runtime_constraints(req.runtime_constraints),
+ scheduling_parameters: req.scheduling_parameters,
+ secret_mounts: req.secret_mounts,
+ runtime_token: req.runtime_token,
+ runtime_user_uuid: runtime_user.uuid,
+ runtime_auth_scopes: runtime_auth_scopes
+ }
+ end
act_as_system_user do
if req.use_existing && (reusable = find_reusable(c_attrs))
reusable
candidates = candidates.where_serialized(:runtime_constraints, resolve_runtime_constraints(attrs[:runtime_constraints]), md5: true)
log_reuse_info(candidates) { "after filtering on runtime_constraints #{attrs[:runtime_constraints].inspect}" }
+ candidates = candidates.where('runtime_user_uuid = ? or (runtime_user_uuid is NULL and runtime_auth_scopes is NULL)',
+ attrs[:runtime_user_uuid])
+ log_reuse_info(candidates) { "after filtering on runtime_user_uuid #{attrs[:runtime_user_uuid].inspect}" }
+
+ candidates = candidates.where('runtime_auth_scopes = ? or (runtime_user_uuid is NULL and runtime_auth_scopes is NULL)',
+ SafeJSON.dump(attrs[:runtime_auth_scopes].sort))
+ log_reuse_info(candidates) { "after filtering on runtime_auth_scopes #{attrs[:runtime_auth_scopes].inspect}" }
+
log_reuse_info { "checking for state=Complete with readable output and log..." }
select_readable_pdh = Collection.
[Complete, Cancelled].include?(self.state)
end
+ def self.for_current_token
+ return if !current_api_client_authorization
+ _, _, _, container_uuid = Thread.current[:token].split('/')
+ if container_uuid.nil?
+ Container.where(auth_uuid: current_api_client_authorization.uuid).first
+ else
+ Container.where('auth_uuid=? or (uuid=? and runtime_token=?)',
+ current_api_client_authorization.uuid,
+ container_uuid,
+ current_api_client_authorization.token).first
+ end
+ end
+
protected
def fill_field_defaults
permitted.push(:owner_uuid, :command, :container_image, :cwd,
:environment, :mounts, :output_path, :priority,
:runtime_constraints, :scheduling_parameters,
- :secret_mounts)
+ :secret_mounts, :runtime_token,
+ :runtime_user_uuid, :runtime_auth_scopes)
end
case self.state
def assign_auth
if self.auth_uuid_changed?
- return errors.add :auth_uuid, 'is readonly'
+ return errors.add :auth_uuid, 'is readonly'
end
if not [Locked, Running].include? self.state
# don't need one
# already have one
return
end
- cr = ContainerRequest.
- where('container_uuid=? and priority>0', self.uuid).
- order('priority desc').
- first
- if !cr
- return errors.add :auth_uuid, "cannot be assigned because priority <= 0"
+ if self.runtime_token.nil?
+ if self.runtime_user_uuid.nil?
+ # legacy behavior, we don't have a runtime_user_uuid so get
+ # the user from the highest priority container request, needed
+ # when performing an upgrade and there are queued containers,
+ # and some tests.
+ cr = ContainerRequest.
+ where('container_uuid=? and priority>0', self.uuid).
+ order('priority desc').
+ first
+ if !cr
+ return errors.add :auth_uuid, "cannot be assigned because priority <= 0"
+ end
+ self.runtime_user_uuid = cr.modified_by_user_uuid
+ self.runtime_auth_scopes = ["all"]
+ end
+
+ # generate a new token
+ self.auth = ApiClientAuthorization.
+ create!(user_id: User.find_by_uuid(self.runtime_user_uuid).id,
+ api_client_id: 0,
+ scopes: self.runtime_auth_scopes)
end
- self.auth = ApiClientAuthorization.
- create!(user_id: User.find_by_uuid(cr.modified_by_user_uuid).id,
- api_client_id: 0)
end
def sort_serialized_attrs
if self.scheduling_parameters_changed?
self.scheduling_parameters = self.class.deep_sort_hash(self.scheduling_parameters)
end
+ if self.runtime_auth_scopes_changed?
+ self.runtime_auth_scopes = self.runtime_auth_scopes.sort
+ end
end
def update_secret_mounts_md5
end
end
- def scrub_secret_mounts
+ def scrub_secrets
# this runs after update_secret_mounts_md5, so the
# secret_mounts_md5 will still reflect the secrets that are being
# scrubbed here.
if self.state_changed? && self.final?
self.secret_mounts = {}
+ self.runtime_token = nil
end
end
container_image: self.container_image,
mounts: self.mounts,
runtime_constraints: self.runtime_constraints,
- scheduling_parameters: self.scheduling_parameters
+ scheduling_parameters: self.scheduling_parameters,
+ secret_mounts: self.secret_mounts_was,
+ runtime_token: self.runtime_token_was,
+ runtime_user_uuid: self.runtime_user_uuid,
+ runtime_auth_scopes: self.runtime_auth_scopes
}
c = Container.create! c_attrs
retryable_requests.each do |cr|
validate :validate_state_change
validate :check_update_whitelist
validate :secret_mounts_key_conflict
- before_save :scrub_secret_mounts
+ validate :validate_runtime_token
+ before_save :scrub_secrets
before_create :set_requesting_container_uuid
before_destroy :set_priority_zero
after_save :update_priority
AttrsPermittedAlways = [:owner_uuid, :state, :name, :description, :properties]
AttrsPermittedBeforeCommit = [:command, :container_count_max,
:container_image, :cwd, :environment, :filters, :mounts,
- :output_path, :priority,
+ :output_path, :priority, :runtime_token,
:runtime_constraints, :state, :container_uuid, :use_existing,
:scheduling_parameters, :secret_mounts, :output_name, :output_ttl]
end
def logged_attributes
- super.except('secret_mounts')
+ super.except('secret_mounts', 'runtime_token')
end
def state_transitions
end
def skip_uuid_read_permission_check
- # XXX temporary until permissions are sorted out.
- %w(modified_by_client_uuid container_uuid requesting_container_uuid)
+ # The uuid_read_permission_check prevents users from making
+ # references to objects they can't view. However, in this case we
+ # don't want to do that check since there's a circular dependency
+ # where user can't view the container until the user has
+ # constructed the container request that references the container.
+ %w(container_uuid)
end
def finalize_if_needed
end
def self.full_text_searchable_columns
- super - ["mounts", "secret_mounts", "secret_mounts_md5"]
+ super - ["mounts", "secret_mounts", "secret_mounts_md5", "runtime_token"]
end
protected
end
end
- def scrub_secret_mounts
+ def validate_runtime_token
+ if !self.runtime_token.nil? && self.runtime_token_changed?
+ if !runtime_token[0..2] == "v2/"
+ errors.add :runtime_token, "not a v2 token"
+ return
+ end
+ if ApiClientAuthorization.validate(token: runtime_token).nil?
+ errors.add :runtime_token, "failed validation"
+ end
+ end
+ end
+
+ def scrub_secrets
if self.state == Final
self.secret_mounts = {}
+ self.runtime_token = nil
end
end
def get_requesting_container
return self.requesting_container_uuid if !self.requesting_container_uuid.nil?
- return if !current_api_client_authorization
- if (c = Container.where('auth_uuid=?', current_api_client_authorization.uuid).select([:uuid, :priority]).first)
- return c
- end
+ Container.for_current_token
end
end
--- /dev/null
+class AddContainerRuntimeToken < ActiveRecord::Migration
+ def change
+ add_column :container_requests, :runtime_token, :text, :null => true
+ add_column :containers, :runtime_user_uuid, :text, :null => true
+ add_column :containers, :runtime_auth_scopes, :jsonb, :null => true
+ end
+end
--- /dev/null
+class AddRuntimeTokenToContainer < ActiveRecord::Migration
+ def change
+ add_column :containers, :runtime_token, :text, :null => true
+ end
+end
log_uuid character varying(255),
output_name character varying(255) DEFAULT NULL::character varying,
output_ttl integer DEFAULT 0 NOT NULL,
- secret_mounts jsonb DEFAULT '{}'::jsonb
+ secret_mounts jsonb DEFAULT '{}'::jsonb,
+ runtime_token text
);
scheduling_parameters text,
secret_mounts jsonb DEFAULT '{}'::jsonb,
secret_mounts_md5 character varying DEFAULT '99914b932bd37a50b983c5e7c90ae93b'::character varying,
- runtime_status jsonb DEFAULT '{}'::jsonb
+ runtime_status jsonb DEFAULT '{}'::jsonb,
+ runtime_user_uuid text,
+ runtime_auth_scopes jsonb,
+ runtime_token text
);
INSERT INTO schema_migrations (version) VALUES ('20181001175023');
INSERT INTO schema_migrations (version) VALUES ('20181004131141');
+
+INSERT INTO schema_migrations (version) VALUES ('20181005192222');
+
+INSERT INTO schema_migrations (version) VALUES ('20181011184200');
+
where({group_class: 'project'}).
where('is_trashed = false and trash_at < statement_timestamp()').
update_all('is_trashed = true')
+
+ # Sweep expired tokens
+ ActiveRecord::Base.connection.execute("DELETE from api_client_authorizations where expires_at <= statement_timestamp()")
end
end
- GET /arvados/v1/collections/zzzzz-4zz18-znfnqtbbv4spc3w
- GET /arvados/v1/collections/zzzzz-4zz18-znfnqtbbv4spc3w/
- GET /arvados/v1/keep_services/accessible
+
+container_runtime_token:
+ uuid: zzzzz-gj3su-2nj68s291f50gd9
+ api_client: untrusted
+ user: container_runtime_token_user
+ api_token: 2d19ue6ofx26o3mm7fs9u6t7hov9um0v92dzwk1o2xed3abprw
+ expires_at: 2038-01-01 00:00:00
+
+crt_user:
+ uuid: zzzzz-gj3su-3r47qqy5ja5d54v
+ api_client: untrusted
+ user: container_runtime_token_user
+ api_token: 13z1tz9deoryml3twep0vsahi4862097pe5lsmesugnkgpgpwk
+ expires_at: 2038-01-01 00:00:00
+
+runtime_token_limited_scope:
+ uuid: zzzzz-gj3su-2fljvypjrr4yr9m
+ api_client: untrusted
+ user: container_runtime_token_user
+ api_token: 1fwc3be1m13qkypix2gd01i4bq5ju483zjfc0cf4babjseirbm
+ expires_at: 2038-01-01 00:00:00
+ scopes: ["GET /"]
delete_at: 2038-01-01T00:00:00Z
manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:expired\n"
name: expired_collection
+ version: 2
+
+expired_collection_past_version:
+ uuid: zzzzz-4zz18-mto52zx1s7oldie
+ current_version_uuid: zzzzz-4zz18-mto52zx1s7sn3ih
+ portable_data_hash: 0b21a217243bfce5617fb9224b95bcb9+49
+ owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
+ created_at: 2014-02-03T17:12:54Z
+ modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
+ modified_by_user_uuid: zzzzz-tpzed-d9tiejq69daie8f
+ modified_at: 2014-02-03T17:17:54Z
+ updated_at: 2014-02-03T17:17:54Z
+ is_trashed: true
+ trash_at: 2001-01-01T00:00:00Z
+ delete_at: 2038-01-01T00:00:00Z
+ manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:expired\n"
+ name: expired_collection original
+ version: 1
trashed_on_next_sweep:
uuid: zzzzz-4zz18-4guozfh77ewd2f0
vcpus: 1
ram: 123
+runtime_token:
+ uuid: zzzzz-xvhdp-11eklkhy0n4dm86
+ owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
+ name: queued
+ state: Committed
+ priority: 1
+ created_at: <%= 2.minute.ago.to_s(:db) %>
+ updated_at: <%= 1.minute.ago.to_s(:db) %>
+ modified_at: <%= 1.minute.ago.to_s(:db) %>
+ modified_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
+ container_image: test
+ cwd: test
+ output_path: test
+ command: ["echo", "hello"]
+ container_uuid: zzzzz-dz642-20isqbkl8xwnsao
+ runtime_token: v2/zzzzz-gj3su-2nj68s291f50gd9/2d19ue6ofx26o3mm7fs9u6t7hov9um0v92dzwk1o2xed3abprw
+ runtime_constraints:
+ vcpus: 1
+ ram: 123
+
# Test Helper trims the rest of the file
auth_uuid: zzzzz-gj3su-ty6lvu9d7u7c2sq
secret_mounts: {}
secret_mounts_md5: 99914b932bd37a50b983c5e7c90ae93b
+
+runtime_token:
+ uuid: zzzzz-dz642-20isqbkl8xwnsao
+ owner_uuid: zzzzz-tpzed-000000000000000
+ state: Locked
+ locked_by_uuid: zzzzz-gj3su-jrriu629zljsnuf
+ priority: 1
+ created_at: 2016-01-11 11:11:11.111111111 Z
+ updated_at: 2016-01-11 11:11:11.111111111 Z
+ container_image: test
+ cwd: test
+ output_path: test
+ command: ["echo", "hello"]
+ runtime_token: v2/zzzzz-gj3su-2nj68s291f50gd9/2d19ue6ofx26o3mm7fs9u6t7hov9um0v92dzwk1o2xed3abprw
+ runtime_user_uuid: zzzzz-tpzed-l3skomkti0c4vg4
+ runtime_auth_scopes: ["all"]
+ runtime_constraints:
+ ram: 12000000000
+ vcpus: 4
+ mounts:
+ /tmp:
+ kind: tmp
+ capacity: 24000000000
+ /var/spool/cwl:
+ kind: tmp
+ capacity: 24000000000
head_uuid: zzzzz-4zz18-d0d8z5wofvfgwad
properties: {}
+crt_user_permission_to_unlinked_docker_image_collection:
+ uuid: zzzzz-o0j2j-20zvdi9b4odcfz3
+ owner_uuid: zzzzz-tpzed-000000000000000
+ created_at: 2014-01-24 20:42:26 -0800
+ modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
+ modified_by_user_uuid: zzzzz-tpzed-000000000000000
+ modified_at: 2014-01-24 20:42:26 -0800
+ updated_at: 2014-01-24 20:42:26 -0800
+ tail_uuid: zzzzz-tpzed-l3skomkti0c4vg4
+ link_class: permission
+ name: can_read
+ head_uuid: zzzzz-4zz18-d0d8z5wofvfgwad
+ properties: {}
+
docker_image_collection_hash:
uuid: zzzzz-o0j2j-dockercollhasha
owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
role: Computational biologist
getting_started_shown: 2015-03-26 12:34:56.789000000 Z
+container_runtime_token_user:
+ owner_uuid: zzzzz-tpzed-000000000000000
+ uuid: zzzzz-tpzed-l3skomkti0c4vg4
+ email: spectator@arvados.local
+ first_name: Spect
+ last_name: Ator
+ identity_url: https://spectator.openid.local
+ is_active: true
+ is_admin: false
+ username: containerruntimetokenuser
+ prefs:
+ profile:
+ organization: example.com
+ role: Computational biologist
+ getting_started_shown: 2015-03-26 12:34:56.789000000 Z
+
inactive_uninvited:
owner_uuid: zzzzz-tpzed-000000000000000
uuid: zzzzz-tpzed-rf2ec3ryh4vb5ma
assert_response 200
end
+ [:admin, :active].each do |user|
+ test "get trashed collection via filters and #{user} user" do
+ uuid = 'zzzzz-4zz18-mto52zx1s7sn3ih' # expired_collection
+ authorize_with user
+ get :index, {
+ filters: [["current_version_uuid", "=", uuid]],
+ include_trash: true,
+ }
+ assert_response 200
+ # Only the current version is returned
+ assert_equal 1, json_response["items"].size
+ end
+ end
+
+ [:admin, :active].each do |user|
+ test "get trashed collection via filters and #{user} user, including its past versions" do
+ uuid = 'zzzzz-4zz18-mto52zx1s7sn3ih' # expired_collection
+ authorize_with :admin
+ get :index, {
+ filters: [["current_version_uuid", "=", uuid]],
+ include_trash: true,
+ include_old_versions: true,
+ }
+ assert_response 200
+ # Both current & past version are returned
+ assert_equal 2, json_response["items"].size
+ end
+ end
+
+ test "trash collection also trash its past versions" do
+ uuid = collections(:collection_owned_by_active).uuid
+ authorize_with :active
+ versions = Collection.where(current_version_uuid: uuid)
+ assert_equal 2, versions.size
+ versions.each do |col|
+ refute col.is_trashed
+ end
+ post :trash, {
+ id: uuid,
+ }
+ assert_response 200
+ versions = Collection.where(current_version_uuid: uuid)
+ assert_equal 2, versions.size
+ versions.each do |col|
+ assert col.is_trashed
+ end
+ end
+
test 'get trashed collection without include_trash' do
uuid = 'zzzzz-4zz18-mto52zx1s7sn3ih' # expired_collection
authorize_with :active
req.reload
assert_equal 'bar', req.secret_mounts['/foo']['content']
end
+
+ test "runtime_token not in #create responses" do
+ authorize_with :active
+
+ post :create, {
+ container_request: minimal_cr.merge(
+ runtime_token: api_client_authorizations(:spectator).token)
+ }
+ assert_response :success
+
+ resp = JSON.parse(@response.body)
+ refute resp.has_key?('runtime_token')
+
+ req = ContainerRequest.where(uuid: resp['uuid']).first
+ assert_equal api_client_authorizations(:spectator).token, req.runtime_token
+ end
+
end
end
end
end
+
+ test 'get runtime_token auth' do
+ authorize_with :dispatch2
+ c = containers(:runtime_token)
+ get :auth, id: c.uuid
+ assert_response :success
+ assert_equal "v2/#{json_response['uuid']}/#{json_response['api_token']}", api_client_authorizations(:container_runtime_token).token
+ assert_equal 'arvados#apiClientAuthorization', json_response['kind']
+ end
+
end
--- /dev/null
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: AGPL-3.0
+
+require 'test_helper'
+
+class ContainerAuthTest < ActionDispatch::IntegrationTest
+ fixtures :all
+
+ test "container token validate, Running, regular auth" do
+ get "/arvados/v1/containers/current", {
+ :format => :json
+ }, {'HTTP_AUTHORIZATION' => "Bearer #{api_client_authorizations(:running_container_auth).token}/#{containers(:running).uuid}"}
+ # Container is Running, token can be used
+ assert_response :success
+ assert_equal containers(:running).uuid, json_response['uuid']
+ end
+
+ test "container token validate, Locked, runtime_token" do
+ get "/arvados/v1/containers/current", {
+ :format => :json
+ }, {'HTTP_AUTHORIZATION' => "Bearer #{api_client_authorizations(:container_runtime_token).token}/#{containers(:runtime_token).uuid}"}
+ # Container is Running, token can be used
+ assert_response :success
+ assert_equal containers(:runtime_token).uuid, json_response['uuid']
+ end
+
+ test "container token validate, Cancelled, runtime_token" do
+ put "/arvados/v1/containers/#{containers(:runtime_token).uuid}", {
+ :format => :json,
+ :container => {:state => "Cancelled"}
+ }, {'HTTP_AUTHORIZATION' => "Bearer #{api_client_authorizations(:dispatch1).token}"}
+ assert_response :success
+ get "/arvados/v1/containers/current", {
+ :format => :json
+ }, {'HTTP_AUTHORIZATION' => "Bearer #{api_client_authorizations(:container_runtime_token).token}/#{containers(:runtime_token).uuid}"}
+ # Container is Queued, token cannot be used
+ assert_response 401
+ end
+
+ test "container token validate, Running, without optional portion" do
+ get "/arvados/v1/containers/current", {
+ :format => :json
+ }, {'HTTP_AUTHORIZATION' => "Bearer #{api_client_authorizations(:running_container_auth).token}"}
+ # Container is Running, token can be used
+ assert_response :success
+ assert_equal containers(:running).uuid, json_response['uuid']
+ end
+
+ test "container token validate, Locked, runtime_token, without optional portion" do
+ get "/arvados/v1/containers/current", {
+ :format => :json
+ }, {'HTTP_AUTHORIZATION' => "Bearer #{api_client_authorizations(:container_runtime_token).token}"}
+ # runtime_token without container uuid won't return 'current'
+ assert_response 404
+ end
+
+ test "container token validate, wrong container uuid" do
+ get "/arvados/v1/containers/current", {
+ :format => :json
+ }, {'HTTP_AUTHORIZATION' => "Bearer #{api_client_authorizations(:container_runtime_token).token}/#{containers(:running).uuid}"}
+ # Container uuid mismatch, token can't be used
+ assert_response 401
+ end
+end
assert_equal 'barney', json_response['username']
end
+ test "validate unsalted v2 token for remote cluster zbbbb" do
+ auth = api_client_authorizations(:active)
+ token = "v2/#{auth.uuid}/#{auth.api_token}"
+ get '/arvados/v1/users/current', {format: 'json', remote: 'zbbbb'}, {
+ "HTTP_AUTHORIZATION" => "Bearer #{token}"
+ }
+ assert_response :success
+ assert_equal(users(:active).uuid, json_response['uuid'])
+ end
+
+ test 'container request with runtime_token' do
+ [["valid local", "v2/#{api_client_authorizations(:active).uuid}/#{api_client_authorizations(:active).api_token}"],
+ ["valid remote", "v2/zbbbb-gj3su-000000000000000/abc"],
+ ["invalid local", "v2/#{api_client_authorizations(:active).uuid}/fakefakefake"],
+ ["invalid remote", "v2/zbork-gj3su-000000000000000/abc"],
+ ].each do |label, runtime_token|
+ post '/arvados/v1/container_requests', {
+ "container_request" => {
+ "command" => ["echo"],
+ "container_image" => "xyz",
+ "output_path" => "/",
+ "cwd" => "/",
+ "runtime_token" => runtime_token
+ }
+ }, {"HTTP_AUTHORIZATION" => "Bearer #{api_client_authorizations(:active).api_token}"}
+ if label.include? "invalid"
+ assert_response 422
+ else
+ assert_response :success
+ end
+ end
+ end
+
end
# SPDX-License-Identifier: AGPL-3.0
require 'test_helper'
+require 'sweep_trashed_objects'
class ApiClientAuthorizationTest < ActiveSupport::TestCase
include CurrentApiClient
assert_empty ApiClientAuthorization.where(api_token: newtoken), "Destroyed ApiClientAuth is still in database"
end
end
+
+ test "delete expired in SweepTrashedObjects" do
+ assert_not_empty ApiClientAuthorization.where(uuid: api_client_authorizations(:expired).uuid)
+ SweepTrashedObjects.sweep_now
+ assert_empty ApiClientAuthorization.where(uuid: api_client_authorizations(:expired).uuid)
+ end
+
end
[
['running_container_auth', 'zzzzz-dz642-runningcontainr', 501],
- ['active_no_prefs', nil, 0],
+ ['active_no_prefs', nil, 0]
].each do |token, expected, expected_priority|
test "create as #{token} and expect requesting_container_uuid to be #{expected}" do
set_user_from_auth token
end
end
+ test "create as container_runtime_token and expect requesting_container_uuid to be zzzzz-dz642-20isqbkl8xwnsao" do
+ set_user_from_auth :container_runtime_token
+ Thread.current[:token] = "#{Thread.current[:token]}/zzzzz-dz642-20isqbkl8xwnsao"
+ cr = ContainerRequest.create(container_image: "img", output_path: "/tmp", command: ["echo", "foo"])
+ assert_not_nil cr.uuid, 'uuid should be set for newly created container_request'
+ assert_equal 'zzzzz-dz642-20isqbkl8xwnsao', cr.requesting_container_uuid
+ assert_equal 1, cr.priority
+ end
+
[[{"vcpus" => [2, nil]},
lambda { |resolved| resolved["vcpus"] == 2 }],
[{"vcpus" => [3, 7]},
assert_not_equal cr2.container_uuid, cr.container_uuid
end
+ test "Retry on container cancelled with runtime_token" do
+ set_user_from_auth :spectator
+ spec = api_client_authorizations(:active)
+ cr = create_minimal_req!(priority: 1, state: "Committed",
+ runtime_token: spec.token,
+ container_count_max: 2)
+ prev_container_uuid = cr.container_uuid
+
+ c = act_as_system_user do
+ c = Container.find_by_uuid(cr.container_uuid)
+ assert_equal spec.token, c.runtime_token
+ c.update_attributes!(state: Container::Locked)
+ c.update_attributes!(state: Container::Running)
+ c
+ end
+
+ cr.reload
+ assert_equal "Committed", cr.state
+ assert_equal prev_container_uuid, cr.container_uuid
+ prev_container_uuid = cr.container_uuid
+
+ act_as_system_user do
+ c.update_attributes!(state: Container::Cancelled)
+ end
+
+ cr.reload
+ assert_equal "Committed", cr.state
+ assert_not_equal prev_container_uuid, cr.container_uuid
+ prev_container_uuid = cr.container_uuid
+
+ c = act_as_system_user do
+ c = Container.find_by_uuid(cr.container_uuid)
+ assert_equal spec.token, c.runtime_token
+ c.update_attributes!(state: Container::Cancelled)
+ c
+ end
+
+ cr.reload
+ assert_equal "Final", cr.state
+ assert_equal prev_container_uuid, cr.container_uuid
+
+ end
+
test "Output collection name setting using output_name with name collision resolution" do
set_user_from_auth :active
output_name = 'unimaginative name'
secret_mounts: sm)
assert_equal [:secret_mounts], cr.errors.messages.keys
end
+
+ test "using runtime_token" do
+ set_user_from_auth :spectator
+ spec = api_client_authorizations(:active)
+ cr = create_minimal_req!(state: "Committed", runtime_token: spec.token, priority: 1)
+ cr.save!
+ c = Container.find_by_uuid cr.container_uuid
+ lock_and_run c
+ assert_nil c.auth_uuid
+ assert_equal c.runtime_token, spec.token
+
+ assert_not_nil ApiClientAuthorization.find_by_uuid(spec.uuid)
+
+ act_as_system_user do
+ c.update_attributes!(state: Container::Complete,
+ exit_code: 0,
+ output: '1f4b0bc7583c2a7f9102c395f4ffc5e3+45',
+ log: 'fa7aeb5140e2848d39b416daeef4ffc5+45')
+ end
+
+ cr.reload
+ c.reload
+ assert_nil cr.runtime_token
+ assert_nil c.runtime_token
+ end
+
+ test "invalid runtime_token" do
+ set_user_from_auth :active
+ spec = api_client_authorizations(:spectator)
+ assert_raises(ArgumentError) do
+ cr = create_minimal_req!(state: "Committed", runtime_token: "#{spec.token}xx")
+ cr.save!
+ end
+ end
end
"var" => "val",
},
secret_mounts: {},
+ runtime_user_uuid: "zzzzz-tpzed-xurymjxw79nv3jz",
+ runtime_auth_scopes: ["all"]
}
+ def request_only attrs
+ attrs.reject {|k| [:runtime_user_uuid, :runtime_auth_scopes].include? k}
+ end
+
def minimal_new attrs={}
- cr = ContainerRequest.new DEFAULT_ATTRS.merge(attrs)
+ cr = ContainerRequest.new request_only(DEFAULT_ATTRS.merge(attrs))
cr.state = ContainerRequest::Committed
- act_as_user users(:active) do
- cr.save!
- end
+ cr.save!
c = Container.find_by_uuid cr.container_uuid
assert_not_nil c
return c, cr
end
test "Container serialized hash attributes sorted before save" do
+ set_user_from_auth :active
env = {"C" => "3", "B" => "2", "A" => "1"}
m = {"F" => {"kind" => "3"}, "E" => {"kind" => "2"}, "D" => {"kind" => "1"}}
rc = {"vcpus" => 1, "ram" => 1, "keep_cache_ram" => 1}
end
test "find_reusable method should select higher priority queued container" do
+ Rails.configuration.log_reuse_decisions = true
set_user_from_auth :active
common_attrs = REUSABLE_COMMON_ATTRS.merge({environment:{"var" => "queued"}})
c_low_priority, _ = minimal_new(common_attrs.merge({use_existing:false, priority:1}))
log: 'ea10d51bcf88862dbcc36eb292017dfd+45',
}
- cr = ContainerRequest.new common_attrs
+ cr = ContainerRequest.new request_only(common_attrs)
cr.use_existing = false
cr.state = ContainerRequest::Committed
cr.save!
c_output1 = Container.where(uuid: cr.container_uuid).first
- cr = ContainerRequest.new common_attrs
+ cr = ContainerRequest.new request_only(common_attrs)
cr.use_existing = false
cr.state = ContainerRequest::Committed
cr.save!
c_output2.update_attributes!({state: Container::Running})
c_output2.update_attributes!(completed_attrs.merge({log: log1, output: out2}))
- reused = Container.resolve(ContainerRequest.new(common_attrs))
+ set_user_from_auth :active
+ reused = Container.resolve(ContainerRequest.new(request_only(common_attrs)))
assert_equal c_output1.uuid, reused.uuid
end
Container.find_reusable(REUSABLE_COMMON_ATTRS)
end
+ def runtime_token_attr tok
+ auth = api_client_authorizations(tok)
+ {runtime_user_uuid: User.find_by_id(auth.user_id).uuid,
+ runtime_auth_scopes: auth.scopes,
+ runtime_token: auth.token}
+ end
+
+ test "find_reusable method with same runtime_token" do
+ set_user_from_auth :active
+ common_attrs = REUSABLE_COMMON_ATTRS.merge({use_existing:false, priority:1, environment:{"var" => "queued"}})
+ c1, _ = minimal_new(common_attrs.merge({runtime_token: api_client_authorizations(:container_runtime_token).token}))
+ assert_equal Container::Queued, c1.state
+ reused = Container.find_reusable(common_attrs.merge(runtime_token_attr(:container_runtime_token)))
+ assert_not_nil reused
+ assert_equal reused.uuid, c1.uuid
+ end
+
+ test "find_reusable method with different runtime_token, same user" do
+ set_user_from_auth :active
+ common_attrs = REUSABLE_COMMON_ATTRS.merge({use_existing:false, priority:1, environment:{"var" => "queued"}})
+ c1, _ = minimal_new(common_attrs.merge({runtime_token: api_client_authorizations(:crt_user).token}))
+ assert_equal Container::Queued, c1.state
+ reused = Container.find_reusable(common_attrs.merge(runtime_token_attr(:container_runtime_token)))
+ assert_not_nil reused
+ assert_equal reused.uuid, c1.uuid
+ end
+
+ test "find_reusable method with nil runtime_token, then runtime_token with same user" do
+ set_user_from_auth :crt_user
+ common_attrs = REUSABLE_COMMON_ATTRS.merge({use_existing:false, priority:1, environment:{"var" => "queued"}})
+ c1, _ = minimal_new(common_attrs)
+ assert_equal Container::Queued, c1.state
+ assert_equal users(:container_runtime_token_user).uuid, c1.runtime_user_uuid
+ reused = Container.find_reusable(common_attrs.merge(runtime_token_attr(:container_runtime_token)))
+ assert_not_nil reused
+ assert_equal reused.uuid, c1.uuid
+ end
+
+ test "find_reusable method with different runtime_token, different user" do
+ set_user_from_auth :crt_user
+ common_attrs = REUSABLE_COMMON_ATTRS.merge({use_existing:false, priority:1, environment:{"var" => "queued"}})
+ c1, _ = minimal_new(common_attrs.merge({runtime_token: api_client_authorizations(:active).token}))
+ assert_equal Container::Queued, c1.state
+ reused = Container.find_reusable(common_attrs.merge(runtime_token_attr(:container_runtime_token)))
+ assert_nil reused
+ end
+
+ test "find_reusable method with nil runtime_token, then runtime_token with different user" do
+ set_user_from_auth :active
+ common_attrs = REUSABLE_COMMON_ATTRS.merge({use_existing:false, priority:1, environment:{"var" => "queued"}})
+ c1, _ = minimal_new(common_attrs.merge({runtime_token: nil}))
+ assert_equal Container::Queued, c1.state
+ reused = Container.find_reusable(common_attrs.merge(runtime_token_attr(:container_runtime_token)))
+ assert_nil reused
+ end
+
+ test "find_reusable method with different runtime_token, different scope, same user" do
+ set_user_from_auth :active
+ common_attrs = REUSABLE_COMMON_ATTRS.merge({use_existing:false, priority:1, environment:{"var" => "queued"}})
+ c1, _ = minimal_new(common_attrs.merge({runtime_token: api_client_authorizations(:runtime_token_limited_scope).token}))
+ assert_equal Container::Queued, c1.state
+ reused = Container.find_reusable(common_attrs.merge(runtime_token_attr(:container_runtime_token)))
+ assert_nil reused
+ end
+
test "Container running" do
+ set_user_from_auth :active
c, _ = minimal_new priority: 1
set_user_from_auth :dispatch1
end
test "Lock and unlock" do
+ set_user_from_auth :active
c, cr = minimal_new priority: 0
set_user_from_auth :dispatch1
end
test "Container queued cancel" do
+ set_user_from_auth :active
c, cr = minimal_new({container_count_max: 1})
set_user_from_auth :dispatch1
assert c.update_attributes(state: Container::Cancelled), show_errors(c)
end
test "Container locked cancel" do
+ set_user_from_auth :active
c, _ = minimal_new
set_user_from_auth :dispatch1
assert c.lock, show_errors(c)
end
test "Container locked cancel with log" do
+ set_user_from_auth :active
c, _ = minimal_new
set_user_from_auth :dispatch1
assert c.lock, show_errors(c)
end
test "Container running cancel" do
+ set_user_from_auth :active
c, _ = minimal_new
set_user_from_auth :dispatch1
c.lock
end
test "Container only set exit code on complete" do
+ set_user_from_auth :active
c, _ = minimal_new
set_user_from_auth :dispatch1
c.lock
end
test "locked_by_uuid can update log when locked/running, and output when running" do
+ set_user_from_auth :active
logcoll = collections(:real_log_collection)
c, cr1 = minimal_new
cr2 = ContainerRequest.new(DEFAULT_ATTRS)
end
test "auth_uuid can set output, progress, runtime_status, state on running container -- but not log" do
+ set_user_from_auth :active
c, _ = minimal_new
set_user_from_auth :dispatch1
c.lock
end
test "not allowed to set output that is not readable by current user" do
+ set_user_from_auth :active
c, _ = minimal_new
set_user_from_auth :dispatch1
c.lock
end
test "other token cannot set output on running container" do
+ set_user_from_auth :active
c, _ = minimal_new
set_user_from_auth :dispatch1
c.lock
end
test "can set trashed output on running container" do
+ set_user_from_auth :active
c, _ = minimal_new
set_user_from_auth :dispatch1
c.lock
end
test "not allowed to set trashed output that is not readable by current user" do
+ set_user_from_auth :active
c, _ = minimal_new
set_user_from_auth :dispatch1
c.lock
{state: Container::Complete, exit_code: 0, output: '1f4b0bc7583c2a7f9102c395f4ffc5e3+45'},
{state: Container::Cancelled},
].each do |final_attrs|
- test "secret_mounts is null after container is #{final_attrs[:state]}" do
+ test "secret_mounts and runtime_token are null after container is #{final_attrs[:state]}" do
+ set_user_from_auth :active
c, cr = minimal_new(secret_mounts: {'/secret' => {'kind' => 'text', 'content' => 'foo'}},
- container_count_max: 1)
+ container_count_max: 1, runtime_token: api_client_authorizations(:active).token)
set_user_from_auth :dispatch1
c.lock
c.update_attributes!(state: Container::Running)
c.reload
assert c.secret_mounts.has_key?('/secret')
+ assert_equal api_client_authorizations(:active).token, c.runtime_token
c.update_attributes!(final_attrs)
c.reload
assert_equal({}, c.secret_mounts)
+ assert_nil c.runtime_token
cr.reload
assert_equal({}, cr.secret_mounts)
+ assert_nil cr.runtime_token
assert_no_secrets_logged
end
end
if err != nil {
return "", err
}
- runner.token = auth.APIToken
+ runner.token = fmt.Sprintf("v2/%s/%s/%s", auth.UUID, auth.APIToken, runner.Container.UUID)
return runner.token, nil
}