patriot-workflow-scheduler 0.6.1

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.
Files changed (79) hide show
  1. checksums.yaml +15 -0
  2. data/bin/patriot +8 -0
  3. data/bin/patriot-init +35 -0
  4. data/lib/patriot.rb +11 -0
  5. data/lib/patriot/command.rb +71 -0
  6. data/lib/patriot/command/base.rb +199 -0
  7. data/lib/patriot/command/command_group.rb +43 -0
  8. data/lib/patriot/command/command_macro.rb +141 -0
  9. data/lib/patriot/command/composite.rb +49 -0
  10. data/lib/patriot/command/parser.rb +78 -0
  11. data/lib/patriot/command/sh_command.rb +42 -0
  12. data/lib/patriot/controller.rb +2 -0
  13. data/lib/patriot/controller/package_controller.rb +81 -0
  14. data/lib/patriot/controller/worker_admin_controller.rb +159 -0
  15. data/lib/patriot/job_store.rb +66 -0
  16. data/lib/patriot/job_store/base.rb +159 -0
  17. data/lib/patriot/job_store/factory.rb +19 -0
  18. data/lib/patriot/job_store/in_memory_store.rb +252 -0
  19. data/lib/patriot/job_store/job.rb +118 -0
  20. data/lib/patriot/job_store/job_ticket.rb +30 -0
  21. data/lib/patriot/job_store/rdb_job_store.rb +353 -0
  22. data/lib/patriot/tool.rb +2 -0
  23. data/lib/patriot/tool/batch_parser.rb +102 -0
  24. data/lib/patriot/tool/patriot_command.rb +48 -0
  25. data/lib/patriot/tool/patriot_commands/execute.rb +92 -0
  26. data/lib/patriot/tool/patriot_commands/job.rb +62 -0
  27. data/lib/patriot/tool/patriot_commands/plugin.rb +41 -0
  28. data/lib/patriot/tool/patriot_commands/register.rb +77 -0
  29. data/lib/patriot/tool/patriot_commands/upgrade.rb +24 -0
  30. data/lib/patriot/tool/patriot_commands/validate.rb +84 -0
  31. data/lib/patriot/tool/patriot_commands/worker.rb +35 -0
  32. data/lib/patriot/tool/patriot_commands/worker_admin.rb +60 -0
  33. data/lib/patriot/util.rb +14 -0
  34. data/lib/patriot/util/config.rb +58 -0
  35. data/lib/patriot/util/config/base.rb +22 -0
  36. data/lib/patriot/util/config/inifile_config.rb +63 -0
  37. data/lib/patriot/util/cron_format_parser.rb +104 -0
  38. data/lib/patriot/util/date_util.rb +200 -0
  39. data/lib/patriot/util/db_client.rb +65 -0
  40. data/lib/patriot/util/db_client/base.rb +142 -0
  41. data/lib/patriot/util/db_client/hash_record.rb +53 -0
  42. data/lib/patriot/util/db_client/record.rb +25 -0
  43. data/lib/patriot/util/logger.rb +24 -0
  44. data/lib/patriot/util/logger/facade.rb +33 -0
  45. data/lib/patriot/util/logger/factory.rb +59 -0
  46. data/lib/patriot/util/logger/log4r_factory.rb +111 -0
  47. data/lib/patriot/util/logger/webrick_log_factory.rb +47 -0
  48. data/lib/patriot/util/param.rb +73 -0
  49. data/lib/patriot/util/retry.rb +30 -0
  50. data/lib/patriot/util/script.rb +52 -0
  51. data/lib/patriot/util/system.rb +120 -0
  52. data/lib/patriot/worker.rb +35 -0
  53. data/lib/patriot/worker/base.rb +153 -0
  54. data/lib/patriot/worker/info_server.rb +90 -0
  55. data/lib/patriot/worker/job_store_server.rb +32 -0
  56. data/lib/patriot/worker/multi_node_worker.rb +157 -0
  57. data/lib/patriot/worker/servlet.rb +23 -0
  58. data/lib/patriot/worker/servlet/job_servlet.rb +128 -0
  59. data/lib/patriot/worker/servlet/worker_status_servlet.rb +44 -0
  60. data/skel/batch/sample/daily/test.pbc +4 -0
  61. data/skel/config/patriot.ini +21 -0
  62. data/skel/public/css/bootstrap.css +2495 -0
  63. data/skel/public/css/original.css +54 -0
  64. data/skel/public/js/bootstrap-alerts.js +124 -0
  65. data/skel/public/js/bootstrap-buttons.js +64 -0
  66. data/skel/public/js/bootstrap-dropdown.js +55 -0
  67. data/skel/public/js/bootstrap-modal.js +260 -0
  68. data/skel/public/js/bootstrap-popover.js +90 -0
  69. data/skel/public/js/bootstrap-scrollspy.js +107 -0
  70. data/skel/public/js/bootstrap-tabs.js +80 -0
  71. data/skel/public/js/bootstrap-twipsy.js +321 -0
  72. data/skel/public/js/jquery-1.6.4.min.js +4 -0
  73. data/skel/public/templates/_jobs.erb +97 -0
  74. data/skel/public/templates/job.erb +119 -0
  75. data/skel/public/templates/jobs.erb +21 -0
  76. data/skel/public/templates/jobs_deleted.erb +6 -0
  77. data/skel/public/templates/layout.erb +103 -0
  78. data/skel/public/templates/state_updated.erb +6 -0
  79. metadata +235 -0
@@ -0,0 +1,90 @@
1
+ module Patriot
2
+ module Worker
3
+ # info server (web management console and for monitoring)
4
+ class InfoServer
5
+
6
+ # configuratio key for port used by this server
7
+ PORT_KEY = 'info_server.port'
8
+ # default port number
9
+ DEFAULT_PORT = '36104'
10
+
11
+ # urls used by this server
12
+ URLS_KEY = 'info_server.urls'
13
+ # mapping from the url to a servlet for the url
14
+ URL_MAP_KEY_PREFIX = 'info_server.urlmap'
15
+
16
+ # configuration key for rack handler used to start this server
17
+ RACK_HANDLER_KEY = 'info_server.rack.handler'
18
+ # default rack handler
19
+ DEFAULT_RACK_HANDLER = 'Rack::Handler::WEBrick'
20
+
21
+ include Patriot::Util::Config
22
+ include Patriot::Util::Logger
23
+
24
+ # @param worker [Patriot::Worker::Base] worker managed through this server
25
+ # @param config [Patriot::Util::Config::Bae]
26
+ def initialize(worker, config)
27
+ @logger = create_logger(config)
28
+ @worker = worker
29
+ @config = config
30
+ end
31
+
32
+ # start the server
33
+ def start_server
34
+ port = @config.get(Patriot::Worker::InfoServer::PORT_KEY,
35
+ Patriot::Worker::InfoServer::DEFAULT_PORT)
36
+ if port.nil?
37
+ @logger.info("port is not set. starting info server is skipped")
38
+ return
39
+ end
40
+ @server_thread = Thread.new do
41
+ begin
42
+ @handler = eval(@config.get(RACK_HANDLER_KEY, DEFAULT_RACK_HANDLER))
43
+ app = Rack::URLMap.new(get_url_map)
44
+ app = Rack::CommonLogger.new(app, build_access_logger)
45
+ @handler.run app, {:Port => port, :Host => '0.0.0.0'}
46
+ rescue => e
47
+ @logger.error e
48
+ end
49
+ end
50
+ @logger.info "info server has started"
51
+ return true
52
+ end
53
+
54
+ # @return [Hash<String, Sinatra::Base>]
55
+ def get_url_map
56
+ urls = @config.get(URLS_KEY, nil)
57
+ if urls.nil?
58
+ urlmap = {"/jobs" => Patriot::Worker::Servlet::JobServlet,
59
+ "/worker" => Patriot::Worker::Servlet::WorkerStatusServlet}
60
+ else
61
+ urlmap = {}
62
+ urls = [url] unless urls.is_a? Array
63
+ urls.each do |u|
64
+ servlet = eval( @config.get("#{URL_MAP_KEY_PREFIX}.#{u}") )
65
+ u = "/#{u}" unless u.start_with?("/")
66
+ urlmap[u] = servlet
67
+ end
68
+ end
69
+ urlmap.values.each{|servlet| servlet.configure(@worker, @config)}
70
+ return urlmap
71
+ end
72
+
73
+ def build_access_logger
74
+ config = load_config(:path => @config.path, :type => 'web', :ignore_plugin => true)
75
+ return Patriot::Util::Logger::Factory.create_logger(@hanlder.class.to_s, config)
76
+ end
77
+ private :build_access_logger
78
+
79
+ # instruct to shutdown server
80
+ def shutdown_server
81
+ return false if @server.nil?
82
+ unless @server_thread.nil?
83
+ @handler.shutdown
84
+ @logger.info "info server shutdowned"
85
+ end
86
+ end
87
+
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,32 @@
1
+ require 'thread'
2
+ require 'monitor'
3
+
4
+ module Patriot
5
+ module Worker
6
+ # a worker as info server
7
+ class JobStoreServer < Base
8
+
9
+ # @see Patriot::Worker::Base#build_infoserver
10
+ def build_infoserver
11
+ return Patriot::Worker::InfoServer.new(self,@config)
12
+ end
13
+
14
+ # @see Patriot::Worker::Base#init_worker
15
+ def init_worker
16
+ end
17
+
18
+ # @see Patriot::Worker::Base#run_worker
19
+ def run_worker
20
+ while(@status != Patriot::Worker::Status::SHUTDOWN)
21
+ sleep @cycle
22
+ end
23
+ end
24
+
25
+ # @see Patriot::Worker::Base#stop_worker
26
+ def stop_worker
27
+ @logger.info "terminated"
28
+ end
29
+
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,157 @@
1
+ require 'thread'
2
+ require 'monitor'
3
+
4
+ module Patriot
5
+ module Worker
6
+
7
+ # a worker implementation which can host multiple node on one process
8
+ class MultiNodeWorker < Base
9
+
10
+ include MonitorMixin
11
+
12
+ ### type of node
13
+ # handle any jobs without label or same label
14
+ ANY = 'any'
15
+ # handle only jobs with same label
16
+ OWN = 'own'
17
+ # supporte node types
18
+ SUPPORTED_TYPES = [ANY,OWN]
19
+
20
+ ### type of job
21
+ # executed by any node
22
+ ANY_EXCLUDE_TYPE_OWN = 0
23
+ # executed by only same labeled node
24
+ ONLY_SPECIFIED_NODE = 1
25
+ # unknown
26
+ UNEXPECTED = 2
27
+
28
+ # @see Patriot::Worker::Base#init_worker
29
+ def init_worker
30
+ nodes = @config.get('nodes')
31
+ raise "nodes are not configured" if nodes.nil?
32
+ nodes = [nodes] unless nodes.is_a?(Array)
33
+ @nodes = {}
34
+ nodes.each do |n|
35
+ node_config = get_node_config(@config, n)
36
+ raise "node #{n} is not configured" if node_config.nil?
37
+ @nodes[n] = {:queue => Queue.new }.merge(node_config)
38
+ end
39
+ end
40
+
41
+ def get_node_config(config, n)
42
+ type = config.get("node.#{n}.type")
43
+ threads = config.get("node.#{n}.threads")
44
+ raise "the number of threads for node #{n} is not set" if threads.nil?
45
+ raise "unsupported node type: #{n} with #{type} " unless SUPPORTED_TYPES.include?(type)
46
+ return {:type => type, :threads => threads.to_i}
47
+ end
48
+ private :get_node_config
49
+
50
+ # @see Patriot::Worker::Base#run_worker
51
+ def run_worker
52
+ @threads = []
53
+ # create node threads for job execution
54
+ @nodes.each do |node,conf|
55
+ 1.upto(conf[:threads]) do |i|
56
+ @threads << create_thread(node, i, conf[:queue])
57
+ end
58
+ end
59
+ # start main thread for updating queues
60
+ Thread.current[:name] = 'main'
61
+ while(alive?)
62
+ if @status == Patriot::Worker::Status::ACTIVE
63
+ begin
64
+ job_tickets = @job_store.get_job_tickets(@host, @nodes.keys, {:fetch_limit => @fetch_limit})
65
+ @logger.info "get #{job_tickets.size} jobs"
66
+ update_queue(job_tickets) unless job_tickets.nil?
67
+ rescue => e
68
+ @logger.error e
69
+ end
70
+ end
71
+ sleep @cycle
72
+ end
73
+ end
74
+
75
+ def create_thread(node, thread_number, queue)
76
+ Thread.start(queue) do |q|
77
+ Thread.current[:name] = "worker_#{node}_#{thread_number}"
78
+ Thread.current[:node] = node
79
+ begin
80
+ while(@status != Patriot::Worker::Status::SHUTDOWN)
81
+ job_ticket = q.pop
82
+ if job_ticket == :TERM
83
+ @logger.info "terminating"
84
+ else
85
+ @logger.debug "fetch job #{job_ticket.job_id}"
86
+ Thread.current[Patriot::Worker::JOB_ID_IN_EXECUTION] = job_ticket.job_id
87
+ execute_job(job_ticket)
88
+ Thread.current[Patriot::Worker::JOB_ID_IN_EXECUTION] = nil
89
+ end
90
+ end
91
+ rescue Exception => e
92
+ @logger.error e
93
+ raise e, "exception in worker thread" , $@
94
+ ensure
95
+ @logger.info "terminated"
96
+ interrupted_job = Thread.current[Patriot::Worker::JOB_ID_IN_EXECUTION]
97
+ @logger.warn "job: #{interrupted_job} could be interrupted." unless interrupted_job.nil?
98
+ end
99
+ end
100
+ end
101
+ private :create_thread
102
+
103
+ # check thread status
104
+ def alive?
105
+ @threads.each do |t|
106
+ if t.status.nil? # thread stopped by some error
107
+ @logger.error "#{t[:name]} : nil "
108
+ @status = Patriot::Worker::Status::SHUTDOWN
109
+ elsif t.status == false # stopped by signal
110
+ @logger.debug "#{t[:name]} : false "
111
+ @status = Patriot::Worker::Status::SHUTDOWN
112
+ else
113
+ @logger.debug "#{t[:name]} : #{t.status}"
114
+ end
115
+ end
116
+ return @status != Patriot::Worker::Status::SHUTDOWN
117
+ end
118
+ private :alive?
119
+
120
+ # update job queues
121
+ def update_queue(job_tickets)
122
+ @nodes.each{|node,conf| conf[:queue].clear }
123
+ job_tickets.each do |job_ticket|
124
+ case type_of_job(job_ticket)
125
+ when ANY_EXCLUDE_TYPE_OWN
126
+ @nodes.each{|node,conf| conf[:queue].push(job_ticket) unless conf[:type]==OWN }
127
+ when ONLY_SPECIFIED_NODE
128
+ @nodes[job_ticket.node][:queue].push(job_ticket)
129
+ else
130
+ @logger.warn "receive unexpected job #{job_ticket.to_s}"
131
+ end
132
+ end
133
+ end
134
+ private :update_queue
135
+
136
+ def type_of_job(job_ticket)
137
+ return ANY_EXCLUDE_TYPE_OWN if job_ticket.node.nil?
138
+ return ONLY_SPECIFIED_NODE if @nodes.has_key?(job_ticket.node)
139
+ return UNEXPECTED;
140
+ end
141
+ private :type_of_job
142
+
143
+ # @see Patriot::Worker::Base#run_worker
144
+ def stop_worker
145
+ @status = Patriot::Worker::Status::SHUTDOWN
146
+ @logger.info "terminating worker"
147
+ @nodes.each do |node,conf|
148
+ conf[:queue].clear
149
+ 1.upto(conf[:threads]) {|i| conf[:queue].push(:TERM) }
150
+ end
151
+ @threads.each{|t| t.join}
152
+ @logger.info "terminated"
153
+ end
154
+
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,23 @@
1
+ require 'sinatra/base'
2
+ require 'sinatra/contrib'
3
+
4
+ module Patriot
5
+ module Worker
6
+ # namespacce infoserve servlet
7
+ module Servlet
8
+ # default limit in searching jobs
9
+ DEFAULT_JOB_LIMIT = 50
10
+ # default offset for searching jobs
11
+ DEFAULT_JOB_OFFSET = 0
12
+
13
+ # configuratio key for admin user name
14
+ USERNAME_KEY = 'info_server.admin.username'
15
+ # configuratio key for admin password
16
+ PASSWORD_KEY = 'info_server.admin.password'
17
+
18
+ require 'patriot/worker/servlet/job_servlet.rb'
19
+ require 'patriot/worker/servlet/worker_status_servlet.rb'
20
+
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,128 @@
1
+ require 'sinatra/base'
2
+ require 'sinatra/contrib'
3
+
4
+ module Patriot
5
+ module Worker
6
+ module Servlet
7
+ # excepton thrown in case job not found
8
+ class JobNotFoundException < Exception; end
9
+ # provide job management functionalities
10
+ class JobServlet < Sinatra::Base
11
+ register Sinatra::Contrib
12
+
13
+ set :public_folder, File.join($home, "public")
14
+ set :views, File.join($home, "public", "templates")
15
+ set :show_exceptions, :after_handler
16
+
17
+ # @param worker [Patriot::Wokrer::Base]
18
+ # @param config [Patriot::Util::Config::Base]
19
+ def self.configure(worker, config)
20
+ @@job_store = worker.job_store
21
+ @@username = config.get(USERNAME_KEY, "")
22
+ @@password = config.get(PASSWORD_KEY, "")
23
+ end
24
+
25
+ ### Helper Methods
26
+ helpers do
27
+ # return link to each job information
28
+ def to_job_link(job_id)
29
+ return "<a href='/jobs/#{ERB::Util.url_encode(job_id)}'>#{job_id}</a>"
30
+ end
31
+ # require authorization for updating
32
+ def protected!
33
+ return if authorized?
34
+ headers['WWW-Authenticate'] = 'Basic Realm="Admin Only"'
35
+ halt 401, "Not Authorized"
36
+ end
37
+ # authorize user (basic authentication)
38
+ def authorized?
39
+ @auth ||= Rack::Auth::Basic::Request.new(request.env)
40
+ return @auth.provided? && @auth.basic? && @auth.credentials && @auth.credentials == [@@username, @@password]
41
+ end
42
+ end
43
+
44
+ get '/' do
45
+ state = params['state'] || Patriot::JobStore::JobState::FAILED
46
+ update_job_size(state)
47
+ query = {:limit => params['limit'] || DEFAULT_JOB_LIMIT,
48
+ :offset => params['offset'] || DEFAULT_JOB_OFFSET}
49
+ query[:filter_exp] = params['filter_exp'] if params.has_key?('filter_exp') && params['filter_exp'] != ""
50
+ jobs = @@job_store.find_jobs_by_state(state.to_i, query)
51
+ jobs ||= []
52
+ respond_with :jobs, {:jobs => jobs, :state => state, :filter_exp => query[:filter_exp]}.merge(query)
53
+ end
54
+
55
+ post '/' do
56
+ protected!
57
+ job_ids = params['job_ids']
58
+ if params['_method'] == 'put'
59
+ state = params['state']
60
+ set_state_of_jobs(job_ids, state)
61
+ elsif params['_method'] == 'delete'
62
+ delete_jobs(job_ids)
63
+ else
64
+ raise NotImplementedError
65
+ end
66
+ end
67
+
68
+ get '/:job_id' do
69
+ job_id = params[:job_id]
70
+ history_size = params['history_size'] || 1
71
+ job = @@job_store.get(job_id, {:include_dependency => true})
72
+ raise JobNotFoundException, job_id if job.nil?
73
+ update_job_size(job[Patriot::Command::STATE_ATTR])
74
+ histories = @@job_store.get_execution_history(job_id, {:limit => history_size})
75
+ # respond_with :job, {:job => job, :histories => histories}
76
+ respond_to do |f|
77
+ f.on('text/html'){ erb :job, :locals => {:job => job, :histories => histories} }
78
+ # for monitoring
79
+ f.on('*/*'){ JSON.generate(job.attributes.merge({'job_id' => job_id})) }
80
+ end
81
+ end
82
+
83
+ post '/:job_id' do
84
+ protected!
85
+ if params['_method'] == 'put'
86
+ set_state_of_jobs([params['job_id']], params['state'], {:include_subsequent => params['include_subsequent'] == 'true'})
87
+ respond_with :state_updated, {:jobs => job_ids, :state => state}
88
+ else
89
+ raise NotImplementedError
90
+ end
91
+ end
92
+
93
+ error JobNotFoundException do
94
+ "Job #{env['sinatra.error'].message} is not found"
95
+ end
96
+
97
+ # update jobs size
98
+ def update_job_size(state)
99
+ @size = @@job_store.get_job_size(:ignore_states => [Patriot::JobStore::JobState::SUCCEEDED])
100
+ end
101
+
102
+ # @private
103
+ # update state of jobs
104
+ def set_state_of_jobs(job_ids, state, opts = {})
105
+ opts = {:include_subsequent => false}.merge(opts)
106
+ update_id = Time.now.to_i
107
+ @@job_store.set_state(update_id, job_ids, state)
108
+ if opts[:include_subsequent]
109
+ @@job_store.process_subsequent(job_ids) do |job_store, jobs|
110
+ @@job_store.set_state(update_id, jobs.map(&:job_id), state)
111
+ end
112
+ end
113
+ update_job_size(state)
114
+ respond_with :state_updated, {:jobs => job_ids, :state => state}
115
+ end
116
+ private :set_state_of_jobs
117
+
118
+ def delete_jobs(job_ids)
119
+ job_ids.each{|jid| @@job_store.delete_job(jid) }
120
+ update_job_size(Patriot::JobStore::JobState::DISCARDED)
121
+ respond_with :jobs_deleted, {:jobs => job_ids}
122
+ end
123
+ private :delete_jobs
124
+
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,44 @@
1
+ require 'sinatra/base'
2
+ require 'sinatra/contrib'
3
+
4
+ module Patriot
5
+ module Worker
6
+ module Servlet
7
+
8
+ # allow to monitor worker status
9
+ class WorkerStatusServlet < Sinatra::Base
10
+ register Sinatra::Contrib
11
+
12
+ set :public_folder, File.join($home, "public")
13
+ set :views, File.join($home, "public", "templates")
14
+
15
+ # @param worker [Patriot::Worker::Base]
16
+ # @param config [Patriot::Util::Config::Base]
17
+ def self.configure(worker, config)
18
+ @@worker = worker
19
+ end
20
+
21
+ before do
22
+ @worker = @@worker
23
+ end
24
+
25
+ get '/' do
26
+ respond_with :worker, {:worker => @worker} do |f|
27
+ # for monitoring
28
+ f.on('*/*') { JSON.generate(@worker.host => @worker.status)}
29
+ end
30
+ end
31
+
32
+ put '/' do
33
+ new_status = params['status']
34
+ if [Patriot::Worker::Status::ACTIVE, Patriot::Worker::Status::SLEEP ]
35
+ @worker.status = new_status
36
+ else
37
+ return 400
38
+ end
39
+ end
40
+
41
+ end
42
+ end
43
+ end
44
+ end