Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 4 additions & 7 deletions Bourreau/app/models/bourreau_worker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -206,8 +206,7 @@ def process_task_list(tasks_todo_rel) #:nodoc:
worker_log.debug "There are #{tasks_todo.size} ready tasks that will increase activity."

# Get limits from meta data store
@rr.meta.reload # reload limits if needed.
bourreau_max_tasks = @rr.meta[:task_limit_total].to_i # nil or "" or 0 means infinite
bourreau_max_tasks = CpuQuota.max_active_tasks_total_for_remote_resource(@rr_id) # nil means infinite

# Prepare relation for 'active tasks on this Bourreau'
bourreau_active_tasks = CbrainTask.where( :status => ActiveTasks, :bourreau_id => @rr_id )
Expand All @@ -221,9 +220,7 @@ def process_task_list(tasks_todo_rel) #:nodoc:
user_ids = by_user.keys.shuffle # go through users in random order
while user_ids.size > 0 # loop for each user
user_id = user_ids.pop
user_max_tasks = @rr.meta["task_limit_user_#{user_id}".to_sym]
user_max_tasks = @rr.meta[:task_limit_user_default] if user_max_tasks.blank?
user_max_tasks = user_max_tasks.to_i # nil, "" and "0" means unlimited
user_max_tasks = CpuQuota.max_active_tasks_for_user(user_id, @rr_id) # nil means infinite
# Go through tasks in random order, but with non-New states having higher priority
user_tasks = (by_user[user_id].select { |t| t.status == 'New' }).shuffle +
(by_user[user_id].select { |t| t.status != 'New' }).shuffle # tasks are pop()ed
Expand All @@ -233,7 +230,7 @@ def process_task_list(tasks_todo_rel) #:nodoc:

# Bourreau global limit.
# If exceeded, there's nothing more we can do for this cycle of 'do_regular_work'
if bourreau_max_tasks > 0 # i.e. 'if there is a limit configured'
if bourreau_max_tasks # i.e. 'if there is a limit configured'
bourreau_active_tasks_cnt = bourreau_active_tasks.count
if bourreau_active_tasks_cnt >= bourreau_max_tasks
worker_log.info "Bourreau limit: found #{bourreau_active_tasks_cnt} active tasks, but the limit is #{bourreau_max_tasks}. Skipping."
Expand All @@ -243,7 +240,7 @@ def process_task_list(tasks_todo_rel) #:nodoc:

# User specific limit.
# If exceeded, there's nothing more we can do for this user, so we go to the next
if user_max_tasks > 0 # i.e. 'if there is a limit configured'
if user_max_tasks # i.e. 'if there is a limit configured'
user_active_tasks_cnt = bourreau_active_tasks.where( :user_id => user_id ).count
if user_active_tasks_cnt >= user_max_tasks
worker_log.info "User ##{user_id} limit: found #{user_active_tasks_cnt} active tasks, but the limit is #{user_max_tasks}. Skipping."
Expand Down
5 changes: 4 additions & 1 deletion BrainPortal/app/controllers/quotas_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -154,11 +154,13 @@ def update #:nodoc:
@quota.max_cpu_past_week = guess_time_units(quota_params[:max_cpu_past_week]) if quota_params[:max_cpu_past_week].present?
@quota.max_cpu_past_month = guess_time_units(quota_params[:max_cpu_past_month]) if quota_params[:max_cpu_past_month].present?
@quota.max_cpu_ever = guess_time_units(quota_params[:max_cpu_ever]) if quota_params[:max_cpu_ever].present?
@quota.max_active_tasks = quota_params[:max_active_tasks].to_i if quota_params[:max_active_tasks].to_s =~ /\A\s*\d+\s*\z/
@quota.max_active_tasks = nil if quota_params[:max_active_tasks].blank?
end

new_record = @quota.new_record?

if @quota.save_with_logging(current_user, %w( max_bytes max_files max_cpu_past_week max_cpu_past_month max_cpu_ever ))
if @quota.save_with_logging(current_user, %w( max_bytes max_files max_cpu_past_week max_cpu_past_month max_cpu_ever max_active_tasks ))
if new_record
flash[:notice] = "Quota entry was successfully created."
else
Expand Down Expand Up @@ -356,6 +358,7 @@ def cpu_quota_params #:nodoc:
params.require(:quota).permit(
:user_id, :remote_resource_id, :group_id,
:max_cpu_past_week, :max_cpu_past_month, :max_cpu_ever,
:max_active_tasks,
)
end

Expand Down
10 changes: 10 additions & 0 deletions BrainPortal/app/helpers/quotas_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,14 @@ def pretty_quota_current_cpu_usage(quota)
"#{week} last week; #{month} last month; #{ever} total"
end

# Renders the max number of active tasks
# in pretty form, e.g. "(Unlimited)", "(None allowed)" or "3 tasks".
def pretty_max_active_tasks(quota)
mat = quota.max_active_tasks
return "(Unlimited)" if mat.nil?
return "(None allowed)" if mat < 1
return "1 task" if mat == 1
return "#{mat} tasks"
end

end
22 changes: 22 additions & 0 deletions BrainPortal/app/models/cpu_quota.rb
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,28 @@ def exceeded!(user_id, remote_resource_id)
nil
end

#####################################################
# Max Active Tasks Limit Methods
#####################################################

# Returns the maximum number of active tasks, in total, on a remote resource.
# This is stored in the DB as the CpuQuota limit 'max_active_tasks' for the CoreAdmin user.
# Distinct quotas with different group_ids are just lumped together and the minimum is used.
def self.max_active_tasks_total_for_remote_resource(remote_resource_id)
CpuQuota.where(:remote_resource_id => remote_resource_id, :user_id => User.admin.id).minimum(:max_active_tasks) # can be nil
end

# Returns the maximum number of active tasks for a user on a remote resource.
# This is stored in the DB as the CpuQuota limit 'max_active_tasks' for the user,
# or if no specific CpuQuota object exist, by the CpuQuota object for the all users.
# Note that in the case where several quotas exist (when they differ by group_id), the minimal
# value is used and the group_id is not actually used to limit the number of tasks.
def self.max_active_tasks_for_user(user_id, remote_resource_id)
CpuQuota.where(:remote_resource_id => remote_resource_id, :user_id => user_id).minimum(:max_active_tasks) ||
CpuQuota.where(:remote_resource_id => 0 , :user_id => user_id).minimum(:max_active_tasks) ||
CpuQuota.where(:remote_resource_id => remote_resource_id, :user_id => 0 ).minimum(:max_active_tasks)
end

#####################################################
# Validations callbacks
#####################################################
Expand Down
54 changes: 9 additions & 45 deletions BrainPortal/app/views/bourreaux/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -390,65 +390,29 @@
<div class="field_explanation">Optional.</div>
<% end %>

<% t.edit_cell 'meta[task_limit_total]', :header => "Maximum total number of active tasks", :content => (@bourreau.meta["task_limit_total"].blank? ? "Unlimited" : @bourreau.meta["task_limit_total"]) do |f| %>
<%= select_tag 'meta[task_limit_total]',
options_for_select( [ [ "Unlimited", "" ] ] + %w( 1 2 3 4 5 8 10 15 20 25 30 35 40 45 50 60 75 100 200 500 1000 ) ,
@bourreau.meta[:task_limit_total] )
%>
<% end %>

<% t.edit_cell(:cms_extra_qsub_args, :header => "Extra 'qsub' options") do |f| %>
<%= f.text_field :cms_extra_qsub_args, :size => 60 %><br/>
<div class="field_explanation">Optional. Careful, this is inserted as-is in the command-line for submitting jobs.</div>
<% end %>

<% t.edit_cell 'meta[task_limit_user_default]',
:header => "Default maximum number of active tasks for each user",
:content => (@bourreau.meta["task_limit_user_default"].blank? ? "Server's max" : @bourreau.meta["task_limit_user_default"]) do %>
<%= select_tag 'meta[task_limit_user_default]',
options_for_select( [ [ "Server's max", "" ] ] + %w( 1 2 3 4 5 8 10 15 20 25 30 35 40 45 50 60 75 100 200 500 1000 ) ,
@bourreau.meta[:task_limit_user_default] )
%>
<div class="field_explanation">(Unless specified below)</div>
<% end %>

<% tool_config = ToolConfig.where(:bourreau_id => @bourreau.id, :tool_id => nil).first %>
<% tool_config_show_link = tool_config ? (link_to "Show", tool_config_path(tool_config)) : "No tool config" %>
<% tool_config_create_link = link_to "Create", new_tool_config_path(:bourreau_id => @bourreau.id) %>

<% t.edit_cell("Common configuration for all tasks", :content => tool_config_show_link) do %>
<%= tool_config ? tool_config_show_link : tool_config_create_link %>
<% end %>

<% t.empty_cell %>

<% t.edit_cell(:cms_extra_qsub_args, :header => "Extra 'qsub' options", :show_width => 2) do |f| %>
<%= f.text_field :cms_extra_qsub_args, :size => 60 %><br/>
<div class="field_explanation">Optional. Careful, this is inserted as-is in the command-line for submitting jobs.</div>
<% end %>

<% t.edit_cell(:cms_shared_dir, :header => "Path to shared work directory", :show_width => 2) do |f| %>
<%= f.text_field :cms_shared_dir, :size => 60 %><br/>
<div class="field_explanation">Mandatory. This directory must be visible and writable from all nodes.
This is were the work subdirectories for all tasks will be created.</div>
<% end %>

<% content = capture do %>
<%= array_to_table(@users.sort { |a,b| a.login <=> b.login }, :table_class => 'simple', :td_class => 'right', :cols => 4, :fill_by_columns => true) do |user,r,c| %>
<% metkey = "task_limit_user_#{user.id}".to_sym %>
<%= link_to_user_with_tooltip(user) %>:
</td><td class="some_left_right_padding">
<%= @bourreau.meta[metkey].blank? ? "Default" : @bourreau.meta[metkey] %>
<% end %>
<% end %>

<% t.edit_cell 'meta[task_limit_user_default]',
:header => "Maximum number of active tasks per user",
:content => content, :show_width => 2 do %>
<%= array_to_table(@users.sort { |a,b| a.login <=> b.login }, :table_class => 'simple', :td_class => 'right', :cols => 4, :fill_by_columns => true) do |user,r,c| %>
<% metkey = "task_limit_user_#{user.id}".to_sym %>
<%= link_to_user_with_tooltip(user) %>:
</td><td class="some_left_right_padding">
<%= select_tag "meta[#{metkey}]",
options_for_select( [ [ "Default", "" ] ] + %w( 1 2 3 4 5 8 10 15 20 25 30 35 40 45 50 60 75 100 150 200 250 300 400 500 750 1000 ) ,
@bourreau.meta[metkey] )
%>
<% end %>
<% end %>
<% end %>


<%= show_table(@bourreau, :as => :bourreau, :header => "Task Workers Configuration", :edit_condition => @bourreau.has_owner_access?(current_user)) do |t| %>
<% t.edit_cell :workers_instances, :header => "Number of workers" do |f| %>
<%= f.select :workers_instances, [
Expand Down
4 changes: 4 additions & 0 deletions BrainPortal/app/views/quotas/_cpu_quotas_table.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@
:sortable => true,
) { |cq| pretty_quota_cputime(cq.max_cpu_ever, true) }

t.column("Max Active Tasks", :max_active_tasks,
:sortable => true,
) { |cq| pretty_max_active_tasks(cq) }

# This column is a bit misleading: it shows the CURRENT USER's resources for all
# quota records that are DP-wide, and the AFFECTED USER'S resources for the user-specific quotas.
t.column("My Usage") do |cq|
Expand Down
12 changes: 12 additions & 0 deletions BrainPortal/app/views/quotas/_show_cpu_quota.erb
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,18 @@
</div>
<% end %>

<% t.edit_cell(:max_active_tasks, :show_width => 2, :header => "Max Active Tasks", :content => pretty_max_active_tasks(@quota)) do |f| %>
<%= f.text_field :max_active_tasks, :size => 6 %>
<div class="field_explanation">
The maximum number of tasks that can be active at any given time on the Execution Server.
Leave blank to not set a limit. A value of zero will prevent any tasks from being launched.
Note that projects are ignored for these values, and that if several quota records apply
to a user and differ only by project, the minimum value found in that set will be used.
The core Admin account is used to set a maximum number of tasks IN TOTAL for an Execution
server (thus, no limit specific to that admin user can be specified here).
</div>
<% end %>

<% end %>

<P>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
class AddMaxActiveTasksToCpuQuota < ActiveRecord::Migration[5.0]

def up
add_column :quotas, :max_active_tasks, :integer, :after => :max_cpu_ever

unlimited_time = 1000.years.to_i # hopefully big enough

# Migrate all limits from the old convention (in the MetaData store)
# to the new CpuQuota attribute.

Bourreau.all.to_a.each do |bourreau|
bid = bourreau.id

# Create the max entries for each user (and default for all users) for the Bourreau
old_limit_keys = bourreau.meta.keys.map(&:to_s).grep(/\Atask_limit_user_(default|\d+)\z/)
old_limit_keys.each do |lkey|
limit = bourreau.meta[lkey].presence
next unless limit # should never happen, but just in case
uid = (lkey == "task_limit_user_default") ? 0 : lkey.to_s.sub("task_limit_user_","").to_i
q_req = CpuQuota.where(:remote_resource_id => bid, :user_id => uid, :group_id => 0)
quota = q_req.first ||
q_req.new(
:max_cpu_past_week => unlimited_time,
:max_cpu_past_month => unlimited_time,
:max_cpu_ever => unlimited_time,
)
quota.max_active_tasks ||= limit.to_i
quota.save!
end

# Create the TOTAL max entry for the Bourreau; by convention this belongs to the core Admin user
tot_max = bourreau.meta[:task_limit_total]
if tot_max.present? && tot_max.to_i > 0
q_req = CpuQuota.where(:remote_resource_id => bid, :user_id => User.admin.id, :group_id => 0)
quota = q_req.first ||
q_req.new(
:max_cpu_past_week => unlimited_time,
:max_cpu_past_month => unlimited_time,
:max_cpu_ever => unlimited_time,
)
quota.max_active_tasks ||= tot_max.to_i
quota.save!
end

end
rescue => ex
remove_column :quotas, :max_active_tasks
raise ex
end

def down
remove_column :quotas, :max_active_tasks
end

end
3 changes: 2 additions & 1 deletion BrainPortal/db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema.define(version: 20250925185219) do
ActiveRecord::Schema.define(version: 20250929182856) do

create_table "access_profiles", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci" do |t|
t.string "name", null: false
Expand Down Expand Up @@ -269,6 +269,7 @@
t.decimal "max_cpu_past_week", precision: 24
t.decimal "max_cpu_past_month", precision: 24
t.decimal "max_cpu_ever", precision: 24
t.integer "max_active_tasks"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
Expand Down