patriot-workflow-scheduler 0.6.1

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