osc_machete_rails 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +76 -0
- data/LICENSE.txt +22 -0
- data/README.md +14 -0
- data/Rakefile +34 -0
- data/app/assets/javascripts/osc_machete_rails/application.js +13 -0
- data/app/assets/stylesheets/osc_machete_rails/application.css +13 -0
- data/app/controllers/osc_machete_rails/application_controller.rb +4 -0
- data/app/helpers/osc_machete_rails/application_helper.rb +4 -0
- data/app/views/layouts/osc_machete_rails/application.html.erb +14 -0
- data/config/routes.rb +2 -0
- data/lib/generators/active_record/job_model_generator.rb +13 -0
- data/lib/generators/active_record/orm_helpers.rb +6 -0
- data/lib/generators/active_record/templates/job_model.rb +17 -0
- data/lib/generators/active_record/templates/migration.rb +19 -0
- data/lib/generators/active_record/templates/module.rb +7 -0
- data/lib/generators/active_record/templates/workflow_model.rb +34 -0
- data/lib/generators/active_record/workflow_model_generator.rb +30 -0
- data/lib/generators/osc_machete_rails/USAGE +36 -0
- data/lib/generators/osc_machete_rails/erb/erb_generator.rb +5 -0
- data/lib/generators/osc_machete_rails/erb/templates/_form.html.erb +21 -0
- data/lib/generators/osc_machete_rails/erb/templates/edit.html.erb +5 -0
- data/lib/generators/osc_machete_rails/erb/templates/index.html.erb +41 -0
- data/lib/generators/osc_machete_rails/erb/templates/new.html.erb +5 -0
- data/lib/generators/osc_machete_rails/erb/templates/show.html.erb +13 -0
- data/lib/generators/osc_machete_rails/job_helpers.rb +10 -0
- data/lib/generators/osc_machete_rails/job_model/USAGE +28 -0
- data/lib/generators/osc_machete_rails/job_model/job_model_generator.rb +13 -0
- data/lib/generators/osc_machete_rails/resource_route/USAGE +3 -0
- data/lib/generators/osc_machete_rails/resource_route/resource_route_generator.rb +57 -0
- data/lib/generators/osc_machete_rails/scaffold_controller/USAGE +17 -0
- data/lib/generators/osc_machete_rails/scaffold_controller/scaffold_controller_generator.rb +24 -0
- data/lib/generators/osc_machete_rails/scaffold_controller/templates/controller.rb +120 -0
- data/lib/generators/osc_machete_rails/scaffold_generator.rb +35 -0
- data/lib/generators/osc_machete_rails/workflow_model/USAGE +38 -0
- data/lib/generators/osc_machete_rails/workflow_model/workflow_model_generator.rb +12 -0
- data/lib/generators/osc_machete_rails/workflow_template/USAGE +19 -0
- data/lib/generators/osc_machete_rails/workflow_template/templates/main.sh.mustache +18 -0
- data/lib/generators/osc_machete_rails/workflow_template/workflow_template_generator.rb +9 -0
- data/lib/osc_machete_rails/engine.rb +24 -0
- data/lib/osc_machete_rails/helper.rb +25 -0
- data/lib/osc_machete_rails/statusable.rb +199 -0
- data/lib/osc_machete_rails/version.rb +3 -0
- data/lib/osc_machete_rails/workflow.rb +249 -0
- data/lib/osc_machete_rails.rb +12 -0
- data/lib/tasks/osc_machete_rails_tasks.rake +4 -0
- data/test/dummy/README.rdoc +28 -0
- data/test/dummy/Rakefile +6 -0
- data/test/dummy/app/assets/javascripts/application.js +13 -0
- data/test/dummy/app/assets/javascripts/simulations.js +2 -0
- data/test/dummy/app/assets/stylesheets/application.css +13 -0
- data/test/dummy/app/assets/stylesheets/simulations.css +4 -0
- data/test/dummy/app/controllers/application_controller.rb +5 -0
- data/test/dummy/app/controllers/simulations_controller.rb +103 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/helpers/simulations_helper.rb +2 -0
- data/test/dummy/app/models/simulation.rb +26 -0
- data/test/dummy/app/models/simulation_job.rb +15 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/app/views/simulations/_form.html.erb +15 -0
- data/test/dummy/app/views/simulations/edit.html.erb +5 -0
- data/test/dummy/app/views/simulations/index.html.erb +35 -0
- data/test/dummy/app/views/simulations/new.html.erb +5 -0
- data/test/dummy/app/views/simulations/show.html.erb +16 -0
- data/test/dummy/bin/bundle +3 -0
- data/test/dummy/bin/rails +4 -0
- data/test/dummy/bin/rake +4 -0
- data/test/dummy/config/application.rb +23 -0
- data/test/dummy/config/boot.rb +5 -0
- data/test/dummy/config/database.yml +25 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +29 -0
- data/test/dummy/config/environments/production.rb +80 -0
- data/test/dummy/config/environments/test.rb +36 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/test/dummy/config/initializers/inflections.rb +16 -0
- data/test/dummy/config/initializers/mime_types.rb +5 -0
- data/test/dummy/config/initializers/secret_token.rb +12 -0
- data/test/dummy/config/initializers/session_store.rb +3 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +23 -0
- data/test/dummy/config/routes.rb +11 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/db/development.sqlite3 +0 -0
- data/test/dummy/db/migrate/20151230193910_create_simulations.rb +11 -0
- data/test/dummy/db/migrate/20151230193911_create_simulation_jobs.rb +15 -0
- data/test/dummy/db/schema.rb +38 -0
- data/test/dummy/db/test.sqlite3 +0 -0
- data/test/dummy/jobs/simulation/main.sh.mustache +17 -0
- data/test/dummy/log/development.log +0 -0
- data/test/dummy/log/test.log +31198 -0
- data/test/dummy/public/404.html +58 -0
- data/test/dummy/public/422.html +58 -0
- data/test/dummy/public/500.html +57 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/0ff4cb4a6c217771a1336b307c51cf4e +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/13fe41fee1fe35b49d145bcc06610705 +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/2f5173deea6c795b8fdde723bb4b63af +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/357970feca3ac29060c1e3861e2c0953 +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/57261b7c75c19be33229517e39e47528 +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/9e1e594361ba2561e5f6011db99503bb +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/cffd775d018f68ce5dba1ee0d951a994 +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/d771ace226fc8215a3572e0aa35bb0d6 +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/dc292ec300d07016ebcc2de4806208c3 +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/f7cbd26ba1d28d48de824f0e94586655 +0 -0
- data/test/integration/error_handling_test.rb +166 -0
- data/test/integration/statusable_update_status_test.rb +47 -0
- data/test/models/simulation_test.rb +4 -0
- data/test/osc_machete_rails_test.rb +7 -0
- data/test/test_helper.rb +19 -0
- data/test/unit/statusable_test.rb +124 -0
- metadata +320 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
<% if namespaced? -%>
|
|
2
|
+
require_dependency "<%= namespaced_path %>/application_controller"
|
|
3
|
+
|
|
4
|
+
<% end -%>
|
|
5
|
+
<% module_namespacing do -%>
|
|
6
|
+
class <%= controller_class_name %>Controller < ApplicationController
|
|
7
|
+
before_action :set_<%= singular_table_name %>, only: [:show, :edit, :update, :destroy, :submit, :copy]
|
|
8
|
+
|
|
9
|
+
# GET <%= route_url %>
|
|
10
|
+
# GET <%= route_url %>.json
|
|
11
|
+
def index
|
|
12
|
+
@<%= plural_table_name %> = <%= "#{class_name}.preload(:#{job.plural_name})" %>
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# GET <%= route_url %>/1
|
|
16
|
+
# GET <%= route_url %>/1.json
|
|
17
|
+
def show
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# GET <%= route_url %>/new
|
|
21
|
+
def new
|
|
22
|
+
@<%= singular_table_name %> = <%= orm_class.build(class_name) %>
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# GET <%= route_url %>/1/edit
|
|
26
|
+
def edit
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# POST <%= route_url %>
|
|
30
|
+
# POST <%= route_url %>.json
|
|
31
|
+
def create
|
|
32
|
+
@<%= singular_table_name %> = <%= orm_class.build(class_name, "#{singular_table_name}_params") %>
|
|
33
|
+
|
|
34
|
+
respond_to do |format|
|
|
35
|
+
if @<%= orm_instance.save %>
|
|
36
|
+
format.html { redirect_to @<%= singular_table_name %>, notice: <%= "'#{human_name} was successfully created.'" %> }
|
|
37
|
+
format.json { render :show, status: :created, location: @<%= singular_table_name %> }
|
|
38
|
+
else
|
|
39
|
+
format.html { render :new }
|
|
40
|
+
format.json { render json: @<%= orm_instance.errors %>, status: :unprocessable_entity }
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# PATCH/PUT <%= route_url %>/1
|
|
46
|
+
# PATCH/PUT <%= route_url %>/1.json
|
|
47
|
+
def update
|
|
48
|
+
respond_to do |format|
|
|
49
|
+
if @<%= orm_instance.update("#{singular_table_name}_params") %>
|
|
50
|
+
format.html { redirect_to @<%= singular_table_name %>, notice: <%= "'#{human_name} was successfully updated.'" %> }
|
|
51
|
+
format.json { render :show, status: :ok, location: @<%= singular_table_name %> }
|
|
52
|
+
else
|
|
53
|
+
format.html { render :edit }
|
|
54
|
+
format.json { render json: @<%= orm_instance.errors %>, status: :unprocessable_entity }
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# DELETE <%= route_url %>/1
|
|
60
|
+
# DELETE <%= route_url %>/1.json
|
|
61
|
+
def destroy
|
|
62
|
+
respond_to do |format|
|
|
63
|
+
if @<%= orm_instance.destroy %>
|
|
64
|
+
format.html { redirect_to <%= index_helper %>_url, notice: <%= "'#{human_name} was successfully destroyed.'" %> }
|
|
65
|
+
format.json { head :no_content }
|
|
66
|
+
else
|
|
67
|
+
format.html { redirect_to <%= index_helper %>_url, alert: <%= "\"#{human_name} failed to be destroyed: \#{@#{orm_instance.errors}.to_a}\"" %> }
|
|
68
|
+
format.json { render json: @<%= orm_instance.errors %>, status: :internal_server_error }
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# PUT <%= route_url %>/1/submit
|
|
74
|
+
# PUT <%= route_url %>/1/submit.json
|
|
75
|
+
def submit
|
|
76
|
+
respond_to do |format|
|
|
77
|
+
if @<%= singular_table_name %>.submitted?
|
|
78
|
+
format.html { redirect_to <%= index_helper %>_url, alert: <%= "'#{human_name} has already been submitted.'" %> }
|
|
79
|
+
format.json { head :no_content }
|
|
80
|
+
elsif @<%= singular_table_name %>.submit
|
|
81
|
+
format.html { redirect_to <%= index_helper %>_url, notice: <%= "'#{human_name} was successfully submitted.'" %> }
|
|
82
|
+
format.json { head :no_content }
|
|
83
|
+
else
|
|
84
|
+
format.html { redirect_to <%= index_helper %>_url, alert: <%= "\"#{human_name} failed to be submitted: \#{@#{orm_instance.errors}.to_a}\"" %> }
|
|
85
|
+
format.json { render json: @<%= orm_instance.errors %>, status: :internal_server_error }
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# PUT <%= route_url %>/1/copy
|
|
91
|
+
def copy
|
|
92
|
+
@<%= singular_table_name %> = @<%= singular_table_name %>.copy
|
|
93
|
+
|
|
94
|
+
respond_to do |format|
|
|
95
|
+
if @<%= orm_instance.save %>
|
|
96
|
+
format.html { redirect_to @<%= singular_table_name %>, notice: <%= "'#{human_name} was successfully copied.'" %> }
|
|
97
|
+
format.json { render :show, status: :created, location: @<%= singular_table_name %> }
|
|
98
|
+
else
|
|
99
|
+
format.html { redirect_to <%= index_helper %>_url, alert: <%= "\"#{human_name} failed to be copied: \#{@#{orm_instance.errors}.to_a}\"" %> }
|
|
100
|
+
format.json { render json: @<%= orm_instance.errors %>, status: :unprocessable_entity }
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
private
|
|
106
|
+
# Use callbacks to share common setup or constraints between actions.
|
|
107
|
+
def set_<%= singular_table_name %>
|
|
108
|
+
@<%= singular_table_name %> = <%= orm_class.find("#{class_name}.preload(:#{job.plural_name})", "params[:id]") %>
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Only allow a trusted parameter "white list" through.
|
|
112
|
+
def <%= "#{singular_table_name}_params" %>
|
|
113
|
+
<%- if attributes_names.empty? -%>
|
|
114
|
+
params.fetch(:<%= singular_table_name %>, {})
|
|
115
|
+
<%- else -%>
|
|
116
|
+
params.require(:<%= singular_table_name %>).permit!
|
|
117
|
+
<%- end -%>
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
<% end -%>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
require 'rails/generators/rails/scaffold/scaffold_generator'
|
|
2
|
+
|
|
3
|
+
class OscMacheteRails::ScaffoldGenerator < Rails::Generators::ScaffoldGenerator
|
|
4
|
+
source_root File.expand_path('../templates', __FILE__)
|
|
5
|
+
|
|
6
|
+
attr_reader :orig_args
|
|
7
|
+
|
|
8
|
+
def initialize(args, *options)
|
|
9
|
+
@orig_args = args
|
|
10
|
+
super
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# override ModelGenerator
|
|
14
|
+
remove_hook_for :orm
|
|
15
|
+
|
|
16
|
+
# hook for workflow model/migration
|
|
17
|
+
hook_for :workflow_model, type: :boolean
|
|
18
|
+
|
|
19
|
+
# hook for job model/migration
|
|
20
|
+
hook_for :job_model, type: :boolean do |model|
|
|
21
|
+
invoke model, %W[#{singular_table_name}_job #{singular_table_name}:references]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# override hook for adding config/routes
|
|
25
|
+
hook_for :resource_route, required: true
|
|
26
|
+
|
|
27
|
+
# hook for workflow batch script template
|
|
28
|
+
hook_for :workflow_template, type: :boolean
|
|
29
|
+
|
|
30
|
+
# override ScaffoldGenerator
|
|
31
|
+
hook_for :scaffold_controller, required: true
|
|
32
|
+
|
|
33
|
+
# remove scaffold_stylesheet
|
|
34
|
+
remove_hook_for :stylesheet_engine
|
|
35
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
Description:
|
|
2
|
+
Stubs out a new workflow model. Pass the model name, either CamelCased or
|
|
3
|
+
under_scored, and an optional list of attribute pairs as arguments.
|
|
4
|
+
|
|
5
|
+
Attribute pairs are field:type arguments specifying the
|
|
6
|
+
model's attributes. Timestamps are added by default, so you don't have to
|
|
7
|
+
specify them by hand as 'created_at:datetime updated_at:datetime'.
|
|
8
|
+
|
|
9
|
+
You don't have to think up every attribute up front, but it helps to
|
|
10
|
+
sketch out a few so you can start working with the model immediately.
|
|
11
|
+
|
|
12
|
+
This generator invokes your configured ORM and test framework, which
|
|
13
|
+
defaults to ActiveRecord and TestUnit.
|
|
14
|
+
|
|
15
|
+
Available field types:
|
|
16
|
+
|
|
17
|
+
For more information see:
|
|
18
|
+
|
|
19
|
+
`rails generate model --help`
|
|
20
|
+
|
|
21
|
+
Example:
|
|
22
|
+
`rails generate osc_machete_rails:workflow_model Thing`
|
|
23
|
+
|
|
24
|
+
For ActiveRecord and TestUnit it creates:
|
|
25
|
+
Model: app/models/thing.rb
|
|
26
|
+
Test: test/models/thing_test.rb
|
|
27
|
+
Fixtures: test/fixtures/things.yml
|
|
28
|
+
Migration: db/migrate/XXX_create_things.rb
|
|
29
|
+
|
|
30
|
+
Note:
|
|
31
|
+
It will default to using the Job model for its Machete jobs.
|
|
32
|
+
|
|
33
|
+
`rails generate osc_machete_rails:workflow_model Container
|
|
34
|
+
name:string pressure:decimal container_job:jobs`
|
|
35
|
+
|
|
36
|
+
Note:
|
|
37
|
+
This will do all of above as well as set the ContainerJob as its
|
|
38
|
+
Machete jobs.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
class OscMacheteRails::WorkflowModelGenerator < Rails::Generators::NamedBase
|
|
2
|
+
source_root File.expand_path("../templates", __FILE__)
|
|
3
|
+
|
|
4
|
+
def initialize(args, *options)
|
|
5
|
+
args |= %w(staged_dir:string)
|
|
6
|
+
|
|
7
|
+
super
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
argument :attributes, type: :array, default: [], banner: "field[:type][:index] field[:type][:index]"
|
|
11
|
+
hook_for :orm, required: true
|
|
12
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Description:
|
|
2
|
+
Stubs out a new workflow template. Pass the template name, either
|
|
3
|
+
CamelCased or under_scored, and an optional list of attribute pairs as
|
|
4
|
+
arguments.
|
|
5
|
+
|
|
6
|
+
Attribute pairs are field:type arguments specifying the
|
|
7
|
+
model's attributes. Timestamps are added by default, so you don't have to
|
|
8
|
+
specify them by hand as 'created_at:datetime updated_at:datetime'.
|
|
9
|
+
|
|
10
|
+
You don't have to think up every attribute up front, but it helps to
|
|
11
|
+
sketch out a few so you can start working with the model immediately.
|
|
12
|
+
|
|
13
|
+
This generates a batch script in jobs/.
|
|
14
|
+
|
|
15
|
+
Example:
|
|
16
|
+
`rails generate osc_machete_rails:workflow_template Thing pressure:decimal`
|
|
17
|
+
|
|
18
|
+
Creates:
|
|
19
|
+
Template: jobs/thing/main.sh.mustache
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#PBS -N <%= file_name %>
|
|
2
|
+
#PBS -l nodes=1:ppn=12
|
|
3
|
+
#PBS -l walltime=01:00:00
|
|
4
|
+
#PBS -q @oak-batch.osc.edu
|
|
5
|
+
#PBS -j oe
|
|
6
|
+
#PBS -S /bin/bash
|
|
7
|
+
|
|
8
|
+
echo "---Job started at:"
|
|
9
|
+
date
|
|
10
|
+
|
|
11
|
+
<% attributes.each do |attribute| -%>
|
|
12
|
+
echo "<%= attribute.name %> = {{{<%= attribute.name %>}}}"
|
|
13
|
+
<% end -%>
|
|
14
|
+
|
|
15
|
+
sleep 30
|
|
16
|
+
|
|
17
|
+
echo "---Job finished at:"
|
|
18
|
+
date
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
class OscMacheteRails::WorkflowTemplateGenerator < Rails::Generators::NamedBase
|
|
2
|
+
source_root File.expand_path('../templates', __FILE__)
|
|
3
|
+
|
|
4
|
+
argument :attributes, type: :array, default: [], banner: "field[:type][:index] field[:type][:index]"
|
|
5
|
+
|
|
6
|
+
def copy_batch_script
|
|
7
|
+
template "main.sh.mustache", "jobs/#{file_name}/main.sh.mustache"
|
|
8
|
+
end
|
|
9
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module OscMacheteRails
|
|
2
|
+
class Engine < ::Rails::Engine
|
|
3
|
+
isolate_namespace OscMacheteRails
|
|
4
|
+
|
|
5
|
+
config.app_generators do |g|
|
|
6
|
+
g.workflow_model true
|
|
7
|
+
g.job_model true
|
|
8
|
+
g.workflow_template true
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
config.before_initialize do
|
|
12
|
+
OscMacheteRails.update_status_of_all_active_jobs_on_each_request = true
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
config.after_initialize do
|
|
16
|
+
if OscMacheteRails.update_status_of_all_active_jobs_on_each_request
|
|
17
|
+
# set before action on both Engine controllers and main App controllers
|
|
18
|
+
# to update the status of all the active jobs
|
|
19
|
+
::ApplicationController.before_action -> { OscMacheteRails::Statusable.update_status_of_all_active_jobs }
|
|
20
|
+
ApplicationController.before_action -> { OscMacheteRails::Statusable.update_status_of_all_active_jobs }
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module OscMacheteRails
|
|
2
|
+
module Helper
|
|
3
|
+
def status_label(job, tag = :span)
|
|
4
|
+
job ||= OpenStruct.new status: OSC::Machete::Status.not_submitted
|
|
5
|
+
text = job.status.to_s
|
|
6
|
+
|
|
7
|
+
label_class = 'label-default'
|
|
8
|
+
if job.failed?
|
|
9
|
+
label_class = 'label-danger'
|
|
10
|
+
elsif job.passed?
|
|
11
|
+
label_class = 'label-success'
|
|
12
|
+
text = "Completed"
|
|
13
|
+
elsif job.active?
|
|
14
|
+
label_class = 'label-primary'
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
content_tag tag, class: %I(status-label label #{label_class}) do
|
|
18
|
+
text
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
alias_method :job_status_label, :status_label
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
ActionView::Base.send :include, OscMacheteRails::Helper
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
module OscMacheteRails
|
|
2
|
+
# Methods that deal with pbs batch job status management
|
|
3
|
+
# within a Rails ActiveRecord model
|
|
4
|
+
module Statusable
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
delegate :not_submitted?, :submitted?, :completed?, :passed?, :failed?, :active?, to: :status
|
|
8
|
+
|
|
9
|
+
def status=(s)
|
|
10
|
+
super(OSC::Machete::Status.new(s).char)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# getter returns a Status value from CHAR or a Status value
|
|
14
|
+
def status
|
|
15
|
+
OSC::Machete::Status.new(super)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# delete the batch job and update status
|
|
19
|
+
# may raise PBS::Error as it is unhandled here!
|
|
20
|
+
def stop(update: true)
|
|
21
|
+
return unless status.active?
|
|
22
|
+
|
|
23
|
+
job.delete
|
|
24
|
+
update(status: OSC::Machete::Status.failed) if update
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.included(obj)
|
|
28
|
+
# track the classes that include this module
|
|
29
|
+
self.classes << Kernel.const_get(obj.name) unless obj.name.nil?
|
|
30
|
+
|
|
31
|
+
# add class methods
|
|
32
|
+
obj.send(:extend, ClassMethods)
|
|
33
|
+
|
|
34
|
+
begin
|
|
35
|
+
obj.class_eval do
|
|
36
|
+
# Store job object info in a JSON column and replace old column methods
|
|
37
|
+
if respond_to?(:table_exists?) && table_exists? && respond_to?(:column_names) && column_names.include?('job_cache')
|
|
38
|
+
store :job_cache, accessors: [ :script, :pbsid, :host ], coder: JSON
|
|
39
|
+
delegate :script_name, to: :job
|
|
40
|
+
define_method :job_path do
|
|
41
|
+
job.path
|
|
42
|
+
end
|
|
43
|
+
else
|
|
44
|
+
define_method(:job_cache) do
|
|
45
|
+
{
|
|
46
|
+
script: (job_path && script_name) ? Pathname.new(job_path).join(script_name) : nil,
|
|
47
|
+
pbsid: pbsid,
|
|
48
|
+
host: nil
|
|
49
|
+
}
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
rescue StandardError => e
|
|
54
|
+
msg = "\nIf you are precompiling assets, you can ignore this:\n\tError thrown: #{e.inspect}"
|
|
55
|
+
msg += "\n\tError thrown in OscMacheteRails::Statusable.included when trying to add job_cache to the class including Statusable: #{obj.name}."
|
|
56
|
+
msg += "\n\tjob_cache method was not added to class." unless obj.method_defined? :job_cache
|
|
57
|
+
msg += "\n\n"
|
|
58
|
+
STDERR.puts msg
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def self.classes
|
|
64
|
+
@classes ||= []
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# for each Statusable, call update_status! on active jobs
|
|
68
|
+
def self.update_status_of_all_active_jobs
|
|
69
|
+
# to eager load classes, set config.eager_load to true or execute Rails.application.eager_load!
|
|
70
|
+
if self.classes.empty?
|
|
71
|
+
Rails.logger.warn "Statusable.classes Array is empty. This should contain a list of all the classes that include Statusable. " \
|
|
72
|
+
"This could occur if the Rails app is not configured to eager load classes."
|
|
73
|
+
else
|
|
74
|
+
Rails.logger.debug "Updating active job statuses via Statusable.update_status_of_all_active_jobs."
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
self.classes.each do |klass|
|
|
78
|
+
klass.active.to_a.each(&:update_status!) if klass.respond_to?(:active)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# class methods to extend a model with
|
|
83
|
+
module ClassMethods
|
|
84
|
+
# scope to get all of the jobs that are in an active state
|
|
85
|
+
# or have a pbsid
|
|
86
|
+
def active
|
|
87
|
+
# FIXME: what about OR i.e. where
|
|
88
|
+
#
|
|
89
|
+
# status in active_values OR (pbsid != null and status == null)
|
|
90
|
+
#
|
|
91
|
+
# will need to use STRING for the sql instead of this.
|
|
92
|
+
where(status: OSC::Machete::Status.active_values.map(&:char))
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Setter that accepts an OSC::Machete::Job instance
|
|
97
|
+
#
|
|
98
|
+
# @param [Job] new_job The Job object to be assigned to the Statusable instance.
|
|
99
|
+
def job=(new_job)
|
|
100
|
+
if self.has_attribute?(:job_cache)
|
|
101
|
+
self.script = new_job.script_path.to_s
|
|
102
|
+
self.pbsid = new_job.pbsid
|
|
103
|
+
self.host = new_job.host if new_job.respond_to?(:host)
|
|
104
|
+
else
|
|
105
|
+
self.script_name = new_job.script_name
|
|
106
|
+
self.job_path = new_job.path.to_s
|
|
107
|
+
self.pbsid = new_job.pbsid
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
begin
|
|
111
|
+
self.status = new_job.status
|
|
112
|
+
rescue PBS::Error => e
|
|
113
|
+
# a safe default
|
|
114
|
+
self.status = OSC::Machete::Status.queued
|
|
115
|
+
|
|
116
|
+
# log the error
|
|
117
|
+
Rails.logger.error("After submitting the job with pbsid: #{pbsid}," \
|
|
118
|
+
" checking the status raised a PBS::Error: #{e.message}")
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Returns associated OSC::Machete::Job instance
|
|
123
|
+
def job
|
|
124
|
+
OSC::Machete::Job.new(job_cache.symbolize_keys)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Build the results validation method name from script_name attr
|
|
128
|
+
# using ActiveSupport methods
|
|
129
|
+
#
|
|
130
|
+
# Call this using the Rails console to see what method you should implement
|
|
131
|
+
# to support results validation for that job.
|
|
132
|
+
#
|
|
133
|
+
# @return [String] A string representing a validation method name from script_name attr
|
|
134
|
+
# using ActiveSupport methods
|
|
135
|
+
def results_validation_method_name
|
|
136
|
+
File.basename(script_name, ".*").underscore.parameterize('_') + "_results_valid?"
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# A hook that can be overidden with custom code
|
|
140
|
+
# also looks for default validation methods for existing
|
|
141
|
+
# WARNING: THIS USES ActiveSupport::Inflector methods underscore and parameterize
|
|
142
|
+
#
|
|
143
|
+
# @return [Boolean] true if the results script is present
|
|
144
|
+
def results_valid?
|
|
145
|
+
valid = true
|
|
146
|
+
|
|
147
|
+
if self.respond_to? :script_name && !script_name.nil?
|
|
148
|
+
if self.respond_to?(results_validation_method_name)
|
|
149
|
+
valid = self.send(results_validation_method_name)
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
valid
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
#FIXME: should have a unit test for this!
|
|
157
|
+
# job.update_status! will update and save object
|
|
158
|
+
# if submitted? and ! completed? and status changed from previous state
|
|
159
|
+
# force will cause status to update regardless of completion status,
|
|
160
|
+
# redoing the validations. This way, if you are fixing validation methods
|
|
161
|
+
# you can use the Rails console to update the status of a Workflow by doing this:
|
|
162
|
+
#
|
|
163
|
+
# Container.last.jobs.each {|j| j.update_status!(force: true) }
|
|
164
|
+
#
|
|
165
|
+
# Or for a single statusable such as job:
|
|
166
|
+
#
|
|
167
|
+
# job.update_status!(force: true)
|
|
168
|
+
#
|
|
169
|
+
# FIXME: should log whether a validation method was called or
|
|
170
|
+
# throw a warning that no validation method was found (the one that would have been called)
|
|
171
|
+
#
|
|
172
|
+
# @param [Boolean, nil] force Force the update. (Default: false)
|
|
173
|
+
def update_status!(force: false)
|
|
174
|
+
# this will make it easier to differentiate from current_status
|
|
175
|
+
cached_status = status
|
|
176
|
+
|
|
177
|
+
# by default only update if its an active job
|
|
178
|
+
if (cached_status.not_submitted? && pbsid) || cached_status.active? || force
|
|
179
|
+
# get the current status from the system
|
|
180
|
+
current_status = job.status
|
|
181
|
+
|
|
182
|
+
# if job is done, lets re-validate
|
|
183
|
+
if current_status.completed?
|
|
184
|
+
current_status = results_valid? ? OSC::Machete::Status.passed : OSC::Machete::Status.failed
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
if (current_status != cached_status) || force
|
|
188
|
+
self.status = current_status
|
|
189
|
+
self.save
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
rescue PBS::Error => e
|
|
194
|
+
# we log the error but we just don't update the status
|
|
195
|
+
Rails.logger.error("During update_status! call on job with pbsid #{pbsid} and id #{id}" \
|
|
196
|
+
" a PBS::Error was thrown: #{e.message}")
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|