Skip to content

add the ability to launch scripts #2671

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
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
19 changes: 19 additions & 0 deletions apps/dashboard/app/controllers/scripts_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,20 @@ def create
end
end

# POST /projects/:project_id/scripts/:id/submit
# submit the job
def submit
project = Project.find(params[:project_id])
@script = Script.find(params[:id], project.directory)
opts = submit_script_params[:script].to_h.symbolize_keys

if (job_id = @script.submit(opts))
redirect_to(project_path(params[:project_id]), notice: "Successfully submited job #{job_id}.")
else
redirect_to(project_path(params[:project_id]), alert: @script.errors[:submit].last)
end
end

private

def create_script_params
Expand All @@ -34,4 +48,9 @@ def create_script_params
def show_script_params
params.permit(:id, :project_id)
end

def submit_script_params
keys = @script.smart_attributes.map { |sm| sm.id.to_s }
params.permit({ script: keys }, :project_id, :id)
end
end
5 changes: 5 additions & 0 deletions apps/dashboard/app/helpers/scripts_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# frozen_string_literal: true

module ScriptsHelper
include BatchConnect::SessionContextsHelper
end
1 change: 1 addition & 0 deletions apps/dashboard/app/lib/smart_attributes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ module SmartAttributes
require "smart_attributes/attributes/auto_primary_group"
require "smart_attributes/attributes/auto_queues"
require "smart_attributes/attributes/auto_qos"
require "smart_attributes/attributes/auto_scripts"
require "smart_attributes/attributes/bc_account"
require "smart_attributes/attributes/bc_email_on_started"
require "smart_attributes/attributes/bc_num_hours"
Expand Down
46 changes: 46 additions & 0 deletions apps/dashboard/app/lib/smart_attributes/attributes/auto_scripts.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# frozen_string_literal: true

module SmartAttributes
class AttributeFactory
# Build this attribute object. Must specify a valid directory in opts
#
# @param opts [Hash] attribute's options
# @return [Attributes::AutoScripts] the attribute object
def self.build_auto_scripts(opts = {})
dir = Pathname.new(opts[:directory].to_s)
options = if dir.directory? && dir.readable?
Dir.glob("#{dir}/*.{sh,csh,bash,slurm,sbatch,qsub}").map do |file|
[File.basename(file), file]
end
else
[]
end

static_opts = {
options: options
}.merge(opts.without(:options).to_h)

Attributes::AutoScripts.new('auto_scripts', static_opts)
end
end

module Attributes
class AutoScripts < Attribute
def widget
'select'
end

def label(*)
(opts[:label] || 'Script').to_s
end

# Submission hash describing how to submit this attribute
# @param fmt [String, nil] formatting of hash
# @return [Hash] submission hash
def submit(*)
content = File.read(value)
{ script: { content: content } }
end
end
end
end
101 changes: 91 additions & 10 deletions apps/dashboard/app/models/script.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@
class Script
include ActiveModel::Model

attr_reader :title
class ClusterNotFound < StandardError; end

attr_reader :id
attr_reader :title, :id, :project_dir, :smart_attributes

class << self

def scripts_dir(project_dir)
@scripts_dir ||= Pathname.new("#{project_dir}/.ondemand/scripts").tap do |path|
path.mkpath unless path.exist?
Expand Down Expand Up @@ -43,22 +42,35 @@ def next_id(project_dir)
.map(&:to_i)
.max || 0 + 1
end
end

attr_reader :project_dir, :smart_attributes
def batch_clusters
Rails.cache.fetch('script_batch_clusters', expires_in: 4.hours) do
Configuration.job_clusters.reject do |c|
c.kubernetes? || c.linux_host? || c.systemd?
end.map(&:id).map(&:to_s)
end
end
end

def initialize(opts = {})
opts = opts.to_h.with_indifferent_access

@project_dir = opts[:project_dir] || raise(StandardError, 'You must set the project directory')
@id = opts[:id]
@title = opts[:title].to_s
@smart_attributes = build_smart_attributes(opts[:form] || [])
sm_opts = {
form: opts[:form] || [],
attributes: opts[:attributes] || {}
}
add_cluster_to_form(**sm_opts, clusters: Script.batch_clusters)

@smart_attributes = build_smart_attributes(**sm_opts)
end

def build_smart_attributes(form_list)
form_list.map do |form_item_id|
SmartAttributes::AttributeFactory.build(form_item_id, {})
def build_smart_attributes(form: [], attributes: {})
form.map do |form_item_id|
attrs = attributes[form_item_id].to_h.symbolize_keys
SmartAttributes::AttributeFactory.build(form_item_id, attrs)
end
end

Expand All @@ -73,7 +85,32 @@ def to_h
end
end
end
alias_method :inspect, :to_h
alias inspect to_h

# Delegate methods to smart_attributes' getter
#
# @param method_name the method name called
# @param arguments the arguments to the call
# @param block an optional block for the call
def method_missing(method_name, *arguments, &block)
# not a bug here, we want =, not ==
if /^(?<id>[^=]+)$/ =~ method_name.to_s && (attribute = self[id])
attribute.value
else
super
end
end

def respond_to_missing?(method_name, include_private = false)
(/^(?<id>[^=]+)$/ =~ method_name.to_s && self[id]) || super
end

# Find attribute in list using the id of the attribute
# @param id [Object] id of attribute object
# @return [SmartAttribute::Attribute, nil] attribute object if found
def [](id)
smart_attributes.detect { |attribute| attribute == id }
end

def save
@id = Script.next_id(project_dir)
Expand All @@ -86,5 +123,49 @@ def save
false
end

def submit(options)
adapter = adapter(options[:cluster]).job_adapter
render_format = adapter.class.name.split('::').last.downcase

job_script = OodCore::Job::Script.new(**submit_opts(options, render_format))
Dir.chdir(project_dir) do
adapter.submit(job_script)
end
rescue StandardError => e
errors.add(:submit, e.message)
Rails.logger.error("ERROR: #{e.class} - #{e.message}")
nil
end

private

def submit_opts(options, render_format)
smart_attributes.map do |sm|
sm.value = options[sm.id.to_sym]
sm
end.map do |sm|
sm.submit(fmt: render_format)
end.reduce(&:deep_merge)[:script]
end

def adapter(cluster_id)
OodAppkit.clusters[cluster_id] || raise(ClusterNotFound, "Job specifies nonexistent '#{cluster_id}' cluster id.")
end

def add_cluster_to_form(form: [], attributes: {}, clusters: [])
form.prepend('cluster') unless form.include?('cluster')

attributes[:cluster] = if clusters.size > 1
{
widget: 'select',
label: 'Cluster',
options: clusters
}
else
{
value: clusters.first.id.to_s,
fixed: true
}
end
end
end
13 changes: 9 additions & 4 deletions apps/dashboard/app/views/scripts/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,13 @@
<h1><%= @script.title %></h1>


<%= link_to 'Back', project_path(params[:project_id]), class: 'btn btn-default mt-3', title: 'Return to projects page' %>
<%= link_to 'Back', project_path(params[:project_id]), class: 'btn btn-default my-3', title: 'Return to projects page' %>

<hr>
<%= @script.smart_attributes %>
<hr>
<%= bootstrap_form_for(@script, url: submit_project_script_path) do |f| %>
<% @script.smart_attributes.each do |attrib| %>
<%# TODO generate render_format %>
<%= create_widget(f, attrib, format: nil) %>
<% end %>

<%= f.submit t('dashboard.batch_connect_form_launch'), class: "btn btn-primary btn-block" %>
<% end %>
4 changes: 3 additions & 1 deletion apps/dashboard/config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
if Configuration.jobs_app_alpha?
resources :projects do
root 'projects#index'
resources :scripts
resources :scripts do
post 'submit', on: :member
end
end
end

Expand Down