From 13718d585c2a3345556fb79e55d7ce2977135c6a Mon Sep 17 00:00:00 2001 From: Pierre Rioux Date: Thu, 27 Jun 2019 17:14:14 -0400 Subject: [PATCH] Several fixes all bundled together. 1- The controls channel for Bourreau has a new 'StopYourself' command, which is much more efficient than the old way of stopping the rails app using an external script. Fixes #55 . 2- The 'ibc' utility provides access to it with the 'J' command 3- Tooltips in the interface could never be positioned at offset_x set to 0 because 0 is falsy in javascript. Fixed. 4- We no longer ever keep a backup of database.yml on remote sites. 5- Userfiles viewers that have conditions which crash no longer prevent the 'show' page from rendering; instead the viewer is ignored and removed from the available list. Fixes #25 6- Task forms for boutiques tasks will present list inputs in proper order. Fixes #830 7- Support for lists for File inputs in boutiques Fixes #831 --- Bourreau/script/cbrain_remote_ctl | 19 ++---------- .../app/helpers/resource_link_helper.rb | 2 +- BrainPortal/app/models/bourreau.rb | 21 +++++++++++++ BrainPortal/app/models/cbrain_task.rb | 18 ++++++++++- BrainPortal/app/models/userfile.rb | 2 ++ .../lib/interactive_bourreau_control.rb | 31 +++++++++++++------ .../templates/task_params.html.erb.erb | 5 +-- BrainPortal/public/javascripts/cbrain.js | 5 ++- 8 files changed, 72 insertions(+), 31 deletions(-) diff --git a/Bourreau/script/cbrain_remote_ctl b/Bourreau/script/cbrain_remote_ctl index e3d4d4083..3e7b0ac60 100755 --- a/Bourreau/script/cbrain_remote_ctl +++ b/Bourreau/script/cbrain_remote_ctl @@ -47,13 +47,7 @@ # script/rails server thin -d -e -p -b 127.0.0.1 -P pidfile # # and finally removing the database.yml file. Any database.yml file -# already present before doing all this will be backed up in -# -# database.yml.cbrain_remote_bak. -# -# Note that if stdin contains nothing (zero bytes) the current -# database.yml file will be used, or the script will restore and use -# the backup file 'database.yml.cbrain_remote_bak'. +# already present before doing all this will be erased. # # # To stop the Rails app: @@ -197,25 +191,16 @@ fatal("Port argument must be a number greater than 1024 and less than 65530") un db_yml_text = mode == "start" ? (STDIN.read || "") : "" db_file = rails_home + "/config/database.yml" -db_file_bak = rails_home + "/config/database.yml.cbrain_remote_bak" # Install or restore database.yml if necessary db_exists = File.exist?(db_file) -bak_exists = File.exist?(db_file_bak) blank_yml = db_yml_text !~ /\S/ if blank_yml # blank? use what db.yml is already here - if db_exists - # nothing to do - elsif bak_exists - File.rename(db_file_bak,db_file) - else + if ! db_exists fatal("Could not find a database.yml file for the Rails application!") end else # db text is provided to us - if db_exists - File.rename(db_file,db_file_bak) # will crush existing bak file - end # Optional: change stuff in it, depending on the environment unless ENV['CBRAIN_FRONTEND_HOSTNAME'].nil? || ENV['CBRAIN_FRONTEND_HOSTNAME'] =~ /^\s*$/ db_yml_text.sub!(/hostname:\s*\S+/,"hostname: #{ENV['CBRAIN_FRONTEND_HOSTNAME']}") diff --git a/BrainPortal/app/helpers/resource_link_helper.rb b/BrainPortal/app/helpers/resource_link_helper.rb index 9f27740c3..33703f767 100644 --- a/BrainPortal/app/helpers/resource_link_helper.rb +++ b/BrainPortal/app/helpers/resource_link_helper.rb @@ -255,7 +255,7 @@ def link_to_user_with_tooltip(user, cur_user = current_user, options = {}) return "(None)" if user.blank? cb_error "This method requires the first argument to be a User object." unless user.is_a?(User) capture do - html_tool_tip(link_to_user_if_accessible(user,current_user,options), :offset_x => 60 ) do + html_tool_tip(link_to_user_if_accessible(user,current_user,options), :offset_x => 0, :offset_y => 20 ) do ( "
\n" + "#{h(user.full_name)}
\n" + diff --git a/BrainPortal/app/models/bourreau.rb b/BrainPortal/app/models/bourreau.rb index 60ae5b26f..c28de260e 100644 --- a/BrainPortal/app/models/bourreau.rb +++ b/BrainPortal/app/models/bourreau.rb @@ -353,6 +353,15 @@ def build_db_yml_for_tunnel(railsenv) #:nodoc: public + # Utility method to sends a +stop_yourself+ command to a + # Bourreau RemoteResource, whether local or not. + def send_command_stop_yourself + command = RemoteCommand.new( + :command => 'stop_yourself', + ) + send_command(command) + end + # Utility method to send a +get_task_outputs+ command to a # Bourreau RemoteResource, whether local or not. def send_command_get_task_outputs(task_id,run_number=nil,stdout_lim=nil,stderr_lim=nil) @@ -393,6 +402,18 @@ def send_command_alter_tasks(tasks,new_task_status,extra_remote_commands = {}) protected + # This comaand is a bit dangerous: it will first trigger a +stop_workers+ + # command to be sent locally, and then the rails app will kill itself with + # a TERM signal, thus exiting right after the current request. The user should + # really make sure the workers are inactive and there is no other background + # activity. + def self.process_command_stop_yourself(command) + process_command_stop_workers( + command.dup.tap { |com| com.command = "stop_workers" } + ) + Process.kill('TERM',Process.pid) # the rails server will shut down after the current request + end + # Starts Bourreau worker processes. # This also triggers a 'wakeup' command if they are already # started. diff --git a/BrainPortal/app/models/cbrain_task.rb b/BrainPortal/app/models/cbrain_task.rb index 030634472..e65c4d391 100644 --- a/BrainPortal/app/models/cbrain_task.rb +++ b/BrainPortal/app/models/cbrain_task.rb @@ -47,7 +47,7 @@ class CbrainTask < ApplicationRecord validates_presence_of :tool_config_id # Rails5 make belongs_to association required by default - # Task presets have bourreau id set to 0, + # Task presets have bourreau id set to 0, # that's why the :bourreau assocition should be optionnal belongs_to :bourreau, optional: true belongs_to :user @@ -889,8 +889,24 @@ def remove_workdir_archive #:nodoc: archive.destroy true rescue + if archive + archive.tag_ids |= [ self.class.destroyed_archived_tag.id ] + end true end + # This returns (and if necessary creates the first time) + # a Tag object named 'TaskDestroyed', belonging to the main + # admin, that is used to mark any TaskWorkdirArchive that + # a user tried to delete but couldn't (e.g on unaccessible + # or read-only DPs etc). The admin can later find and delete them + # himself. + def self.destroyed_archived_tag #:nodoc: + @_destroyed_task_tag_ ||= + Tag.find_or_create_by( :name => 'TaskDestroyed', + :user_id => User.admin.id, + :group_id => User.admin.own_group.id ) + end + end diff --git a/BrainPortal/app/models/userfile.rb b/BrainPortal/app/models/userfile.rb index d65385495..d9b96b076 100644 --- a/BrainPortal/app/models/userfile.rb +++ b/BrainPortal/app/models/userfile.rb @@ -720,6 +720,8 @@ def initialize_from_hash(atts = {}) #:nodoc: def valid_for?(userfile) #:nodoc: return true if @conditions.empty? @conditions.all? { |condition| condition.call(userfile) } + rescue + false end def ==(other) #:nodoc: diff --git a/BrainPortal/config/console_rc/lib/interactive_bourreau_control.rb b/BrainPortal/config/console_rc/lib/interactive_bourreau_control.rb index 2ad1f5ad0..831e46c06 100644 --- a/BrainPortal/config/console_rc/lib/interactive_bourreau_control.rb +++ b/BrainPortal/config/console_rc/lib/interactive_bourreau_control.rb @@ -52,8 +52,8 @@ def initialize(bourreaux_list = Bourreau.order(:id).all, term_width = nil) @bourreaux = bourreaux_list @width = term_width if term_width.blank? || term_width.to_i < 1 - numrows,numcols = Readline.get_screen_size rescue [25,120] - @width = numcols + _,numcols = Readline.get_screen_size rescue [25,120] + @width = numcols end @selected = {} end @@ -138,7 +138,7 @@ def interactive_control(initial_command = nil) def process_user_letter(letter) #:nodoc: # Validate the user input - if letter !~ /^([haombwitukygsrczqx]|\d+|exit|quit)$/ + if letter !~ /^([haombwitukygsrczqxj]|\d+|exit|quit)$/ puts "Unknown command: #{letter} (ignored)" return false end @@ -157,7 +157,10 @@ def process_user_letter(letter) #:nodoc: W = starts workers T = stops workers U = stops workers and waits to make sure - K = stops bourreaux + K = stops bourreaux (using shell commands) + J = stops workers and bourreaux (using command control + messages; use this only if you're sure no workers + are active and the SSH masters are opened) Y = cycle: stop workers and bourreaux then start bourreaux and workers (replace operation queue) @@ -226,13 +229,14 @@ def process_user_letter(letter) #:nodoc: end # Operation queue commands - if letter =~ /^[bwtku]$/ + if letter =~ /^[bwtkuj]$/ @operations += " " if @operations.present? @operations += "StartBourreaux" if letter == "b" @operations += "StartWorkers" if letter == "w" @operations += "StopBourreaux" if letter == "k" @operations += "StopWorkers" if letter == "t" @operations += "StopWorkersAndWait" if letter == "u" + @operations += "StopAllByCommand" if letter == "j" return false end @@ -341,11 +345,12 @@ def process_user_letter(letter) #:nodoc: def apply_operation(op, bou) #:nodoc: printf "... %18s %-15s : ", op, bou.name res,mess = [ false, "Unknown Operation #{op}" ] - res,mess = start_bourreaux(bou) if op == "StartBourreaux" + res,mess = start_bourreau(bou) if op == "StartBourreaux" res,mess = start_workers(bou) if op == "StartWorkers" - res,mess = stop_bourreaux(bou) if op == "StopBourreaux" + res,mess = stop_bourreau(bou) if op == "StopBourreaux" res,mess = stop_workers(bou) if op == "StopWorkers" res,mess = stop_workers_and_wait(bou) if op == "StopWorkersAndWait" + res,mess = stop_all_by_command(bou) if op == "StopAllByCommand" printf "%s\n", mess == nil ? "(nil)" : mess [ res, mess ] rescue IRB::Abort => ex @@ -381,14 +386,22 @@ def stop_workers_and_wait(b) #:nodoc: [ output.blank? , output.blank? ? "OK" : "Workers still active" ] # message text used to abort sequence, see earlier in code end - def stop_bourreaux(b) #:nodoc: + def stop_bourreau(b) #:nodoc: r1=b.stop rescue nil r2=b.stop_tunnels rescue nil b.update_attribute(:online, false) [ !!(r1 && r2) , "App: #{r1 or false}\tSSH Master: #{r2 or false}" ] end - def start_bourreaux(b) #:nodoc: + def stop_all_by_command(b) + r1=b.send_command_stop_yourself rescue "(Exc)" + r1=r1.command_execution_status if r1.is_a?(RemoteCommand) + r2=b.stop_tunnels rescue nil + b.update_attribute(:online, false) + [ ((r1 == "OK") && r2.present?), "App: #{r1 or false}\tSSH Master: #{r2 or false}" ] + end + + def start_bourreau(b) #:nodoc: r = b.start rescue nil rev = "???" if (r == true) diff --git a/BrainPortal/lib/cbrain_task_generators/templates/task_params.html.erb.erb b/BrainPortal/lib/cbrain_task_generators/templates/task_params.html.erb.erb index c106d5b33..fe903c181 100644 --- a/BrainPortal/lib/cbrain_task_generators/templates/task_params.html.erb.erb +++ b/BrainPortal/lib/cbrain_task_generators/templates/task_params.html.erb.erb @@ -130,7 +130,7 @@ <%% values = @task.params[name.to_sym] if name && values.nil? values = values.is_a?(Enumerable) ? values.compact : [values].compact - first = values.pop + first = values.shift %>
  • @@ -343,7 +343,8 @@ ) <%- else -%> # File input dropdown - dropdown.(id, id, + name = id <%= list ? '+ "[]"' : '' %> + dropdown.(id, name, input_files.map { |f| [ f.id.to_s, f.name ] }, optional: <%= opt %> ) diff --git a/BrainPortal/public/javascripts/cbrain.js b/BrainPortal/public/javascripts/cbrain.js index 462e85ca7..26371b2d1 100644 --- a/BrainPortal/public/javascripts/cbrain.js +++ b/BrainPortal/public/javascripts/cbrain.js @@ -674,8 +674,11 @@ var trigger = $(this); var tool_tip_id = trigger.data("tool-tip-id"); var tool_tip = $("#" + tool_tip_id); - var offset_x = trigger.data("offset-x") || '30'; + var offset_x = trigger.data("offset-x"); var offset_y = trigger.data("offset-y") || '0'; + if (typeof offset_x == 'undefined') { + offset_x = '30'; + } var x = trigger.position().left + parseInt(offset_x, 10); var y = trigger.position().top + parseInt(offset_y, 10);