osc_machete_rails 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|