Skip to content

Commit

Permalink
add a smart attribute for automatically loading modules (#1930)
Browse files Browse the repository at this point in the history
Add a smart attribute for automatically loading modules in the form: "auto_modules_#{app_name}" where app_name is something like matlab or jupyter.  These are also cluster aware meaning they'll hide & show if dynamic bc js is enabled.
  • Loading branch information
johrstrom committed Apr 7, 2022
1 parent fb45e10 commit 1d4a035
Show file tree
Hide file tree
Showing 10 changed files with 148 additions and 19 deletions.
10 changes: 10 additions & 0 deletions apps/dashboard/app/models/hpc_module.rb
Expand Up @@ -23,6 +23,12 @@ def all(cluster)
end
end
end

def all_versions(module_name)
Configuration.job_clusters.map do |cluster|
all(cluster.id).select { |m| m.name == module_name.to_s }
end.flatten.uniq(&:to_s).sort_by(&:version).reverse
end
end

attr_reader :name, :version
Expand All @@ -39,4 +45,8 @@ def to_s
def default?
version.nil?
end

def on_cluster?(cluster_name)
HpcModule.all(cluster_name).any? { |m| m.to_s == self.to_s }
end
end
9 changes: 9 additions & 0 deletions apps/dashboard/config/configuration_singleton.rb
Expand Up @@ -89,6 +89,15 @@ def login_clusters
)
end

# clusters you can submit jobs to
def job_clusters
@job_clusters ||= OodCore::Clusters.new(
OodAppkit.clusters
.select(&:job_allow?)
.reject { |c| c.metadata.hidden }
)
end

# @return [String, nil] version string from VERSION file, or nil if no file avail
def version_from_file(dir)
file = Pathname.new(dir).join("VERSION")
Expand Down
7 changes: 7 additions & 0 deletions apps/dashboard/lib/smart_attributes/attribute_factory.rb
Expand Up @@ -2,13 +2,20 @@ module SmartAttributes
class AttributeFactory
extend ActiveSupport::Autoload

AUTO_MODULES_REX = /\Aauto_modules_([\w]+)\z/.freeze

class << self
# Build an attribute object from an id and its options
# @param id [#to_s] id of attribute
# @param opts [Hash] attribute options
# @return [Attribute] the attribute object
def build(id, opts = {})
id = id.to_s
if id.match?(AUTO_MODULES_REX)
hpc_mod = id.match(AUTO_MODULES_REX)[1]
id = 'auto_modules'
opts = opts.merge({'module' => hpc_mod})
end

path_to_attribute = "smart_attributes/attributes/#{id}"
begin
Expand Down
40 changes: 40 additions & 0 deletions apps/dashboard/lib/smart_attributes/attributes/auto_modules.rb
@@ -0,0 +1,40 @@
# frozen_string_literal: true

module SmartAttributes
class AttributeFactory
# Build this attribute object with defined options
# @param opts [Hash] attribute's options
# @return [Attributes::AutoModules] the attribute object
def self.build_auto_modules(opts = {})
Attributes::AutoModules.new('auto_modules', opts)
end
end

module Attributes
# AutoModules populates a select widget of modules from HpcModule class
# that is cluster aware. Meaning it will attach data-option-for-cluster-X
# attributes to the options.
class AutoModules < Attribute
def initialize(id, opts = {})
super

@hpc_module = @opts[:module]
@id = "#{id}_#{@hpc_module}" # reset the id to be unique from other auto_module_*
end

def widget
'select'
end

def select_choices
HpcModule.all_versions(@hpc_module).map do |mod|
data_opts = Configuration.job_clusters.map do |cluster|
{ "data-option-for-cluster-#{cluster.id}": false } unless mod.on_cluster?(cluster.id)
end.compact

[mod.version, mod.version].concat(data_opts)
end
end
end
end
end
1 change: 1 addition & 0 deletions apps/dashboard/test/fixtures/modules/oakley.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions apps/dashboard/test/fixtures/modules/owens.json

Large diffs are not rendered by default.

15 changes: 0 additions & 15 deletions apps/dashboard/test/fixtures/modules/simple.json

This file was deleted.

Expand Up @@ -140,3 +140,5 @@ form:
- classroom
- classroom_size
- advanced_options
- auto_modules_app_jupyter
- auto_modules_intel
35 changes: 31 additions & 4 deletions apps/dashboard/test/models/hpc_module_test.rb
Expand Up @@ -3,6 +3,13 @@
require 'test_helper'

class HpcModuleTest < ActiveSupport::TestCase

# owens.json and oakley.json in this directory are from real clusters
# (oakley is actually pitzer) from the time this was written.
def fixture_dir
"#{Rails.root}/test/fixtures/modules/"
end

test 'all safely reads from inaccessabile directories' do
with_modified_env({ OOD_MODULE_FILE_DIR: '/dev/null' }) do
assert_equal [], HpcModule.all('owens')
Expand All @@ -18,10 +25,30 @@ class HpcModuleTest < ActiveSupport::TestCase
end
end

test 'reads a simple file' do
with_modified_env({ OOD_MODULE_FILE_DIR: "#{Rails.root}/test/fixtures/modules/" }) do
# NOTE: that there are no duplicates and rstudio has no version
assert_equal(['jupyter/1', 'jupyter/2', 'rstudio'], HpcModule.all('simple').map(&:to_s))
test 'all versions is corrrect, sorted and unique' do
stub_sys_apps
with_modified_env({ OOD_MODULE_FILE_DIR: fixture_dir }) do
expected = [
'app_jupyter/3.1.18', 'app_jupyter/3.0.17', 'app_jupyter/2.3.2', 'app_jupyter/2.2.10',
'app_jupyter/1.2.21', 'app_jupyter/1.2.16', 'app_jupyter/0.35.6'
]
assert_equal(expected, HpcModule.all_versions('app_jupyter').map(&:to_s))
end
end

test 'all versions returns empty array when it cant find' do
stub_sys_apps
with_modified_env({ OOD_MODULE_FILE_DIR: fixture_dir }) do
assert_equal([], HpcModule.all_versions('wont_find').map(&:to_s))
assert_equal([], HpcModule.all_versions(nil).map(&:to_s))
end
end

test 'on_cluster? can find modules' do
stub_sys_apps
with_modified_env({ OOD_MODULE_FILE_DIR: fixture_dir }) do
assert HpcModule.new('app_jupyter', version: '0.35.6').on_cluster?('oakley')
assert !HpcModule.new('app_jupyter', version: '0.35.6').on_cluster?('owens')
end
end

Expand Down
47 changes: 47 additions & 0 deletions apps/dashboard/test/system/batch_connect_test.rb
Expand Up @@ -448,4 +448,51 @@ def setup
click_on('Launch')
verify_bc_alert('sys/bc_jupyter', 'save', err_msg)
end

test 'auto generated modules are dynamic' do
with_modified_env({ OOD_MODULE_FILE_DIR: 'test/fixtures/modules' }) do
visit new_batch_connect_session_context_url('sys/bc_jupyter')
intel_both_clusters = []

# defaults
assert_equal '3.0.17', find_value('auto_modules_app_jupyter')
assert_equal '2021.3.0', find_value('auto_modules_intel')
assert_equal 'owens', find_value('cluster')
# versions not available on owens
assert_equal 'display: none;', find_option_style('auto_modules_app_jupyter', '3.1.18')
assert_equal 'display: none;', find_option_style('auto_modules_app_jupyter', '1.2.16')
assert_equal 'display: none;', find_option_style('auto_modules_app_jupyter', '0.35.6')
assert_equal 'display: none;', find_option_style('auto_modules_intel', '18.0.4')

# select oakley and now they're available
select('oakley', from: bc_ele_id('cluster'))
assert_equal '3.0.17', find_value('auto_modules_app_jupyter')
assert_equal '', find_option_style('auto_modules_app_jupyter', '3.1.18')
assert_equal '', find_option_style('auto_modules_app_jupyter', '1.2.16')
assert_equal '', find_option_style('auto_modules_app_jupyter', '0.35.6')

# and lots of intel versions aren't
assert_equal 'display: none;', find_option_style('auto_modules_intel', '18.0.2')
assert_equal 'display: none;', find_option_style('auto_modules_intel', '18.0.0')
assert_equal 'display: none;', find_option_style('auto_modules_intel', '17.0.5')
assert_equal 'display: none;', find_option_style('auto_modules_intel', '17.0.2')
assert_equal 'display: none;', find_option_style('auto_modules_intel', '16.0.8')
assert_equal 'display: none;', find_option_style('auto_modules_intel', '16.0.3')
end
end
end


# <select class="form-control" name="batch_connect_session_context[auto_modules]" id="batch_connect_session_context_auto_modules"><option value="2021.3.0">2021.3.0</option>
# <option value="19.1.3">19.1.3</option>
# <option value="19.0.5">19.0.5</option>
# <option value="19.0.3">19.0.3</option>
# <option data-option-for-cluster-owens="false" value="18.0.4" style="display: none;">18.0.4</option>
# <option value="18.0.3">18.0.3</option>
# <option data-option-for-cluster-pitzer="false" value="18.0.2" style="">18.0.2</option>
# <option data-option-for-cluster-pitzer="false" value="18.0.0" style="">18.0.0</option>
# <option value="17.0.7">17.0.7</option>
# <option data-option-for-cluster-pitzer="false" value="17.0.5" style="">17.0.5</option>
# <option data-option-for-cluster-pitzer="false" value="17.0.2" style="">17.0.2</option>
# <option data-option-for-cluster-pitzer="false" value="16.0.8" style="">16.0.8</option>
# <option data-option-for-cluster-pitzer="false" value="16.0.3" style="">16.0.3</option></select>

0 comments on commit 1d4a035

Please sign in to comment.