patriot-workflow-scheduler 0.7.2 → 0.8.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.
Files changed (42) hide show
  1. checksums.yaml +8 -8
  2. data/lib/patriot.rb +1 -0
  3. data/lib/patriot/command.rb +11 -11
  4. data/lib/patriot/command/base.rb +3 -3
  5. data/lib/patriot/command/composite.rb +20 -6
  6. data/lib/patriot/command/post_processor.rb +2 -2
  7. data/lib/patriot/command/post_processor/base.rb +3 -2
  8. data/lib/patriot/command/post_processor/mail_notification.rb +3 -3
  9. data/lib/patriot/command/post_processor/retrial.rb +3 -3
  10. data/lib/patriot/command/sh_command.rb +14 -0
  11. data/lib/patriot/controller/worker_admin_controller.rb +13 -8
  12. data/lib/patriot/job_store/base.rb +16 -7
  13. data/lib/patriot/job_store/in_memory_store.rb +121 -29
  14. data/lib/patriot/job_store/job.rb +7 -7
  15. data/lib/patriot/job_store/job_ticket.rb +1 -0
  16. data/lib/patriot/job_store/rdb_job_store.rb +161 -54
  17. data/lib/patriot/tool/patriot_commands/execute.rb +1 -1
  18. data/lib/patriot/tool/patriot_commands/job.rb +6 -4
  19. data/lib/patriot/tool/patriot_commands/register.rb +7 -5
  20. data/lib/patriot/util/config.rb +14 -3
  21. data/lib/patriot/util/config/inifile_config.rb +1 -1
  22. data/lib/patriot/version.rb +3 -0
  23. data/lib/patriot/worker/base.rb +6 -3
  24. data/lib/patriot/worker/info_server.rb +34 -24
  25. data/lib/patriot/worker/servlet.rb +4 -8
  26. data/lib/patriot/worker/servlet/api_servlet_base.rb +40 -0
  27. data/lib/patriot/worker/servlet/index_servlet.rb +32 -0
  28. data/lib/patriot/worker/servlet/job_api_servlet.rb +156 -0
  29. data/lib/patriot/worker/servlet/worker_api_servlet.rb +67 -0
  30. data/skel/batch/sample/daily/test.pbc +1 -1
  31. data/skel/public/css/bootstrap.min.css +7412 -0
  32. data/skel/public/css/original.css +179 -54
  33. data/skel/public/js/patriot-workflow-scheduler-0.8.0.js +82252 -0
  34. data/skel/public/js/patriot-workflow-scheduler-0.8.0.min.js +26 -0
  35. data/skel/public/js/patriot-workflow-scheduler-0.8.0.min.js.map +1 -0
  36. data/skel/public/templates/_jobs.erb +4 -5
  37. data/skel/public/templates/job.erb +2 -4
  38. data/skel/public/templates/layout.erb +10 -10
  39. data/skel/public/views/index.erb +13 -0
  40. metadata +40 -4
  41. data/lib/patriot/worker/servlet/job_servlet.rb +0 -128
  42. data/lib/patriot/worker/servlet/worker_status_servlet.rb +0 -52
@@ -14,20 +14,31 @@ module Patriot
14
14
  # default plugins directory
15
15
  DEFAULT_PLUGIN_DIR = "plugins"
16
16
  # plugin directory
17
- PLUGIN_LIB_DIR = 'lib'
17
+ PLUGIN_LIB_DIR = 'lib'
18
18
  # plugin initiation script
19
19
  PLUGIN_INIT_SCRIPT = 'init.rb'
20
20
  # admin user mail address
21
21
  ADMIN_USER_KEY = 'admin_user'
22
22
 
23
+ # a comma separated list of hosts where workers are running
24
+ WORKER_HOST_KEY = 'worker_hosts'
25
+ # port number used by info server
26
+ INFO_SERVER_PORT_KEY = 'info_server_port'
27
+
28
+ # configuratio key for admin user name
29
+ USERNAME_KEY = 'info_server.admin.username'
30
+ # configuratio key for admin password
31
+ PASSWORD_KEY = 'info_server.admin.password'
32
+
33
+
23
34
  # load configuration file
24
35
  # @param option [Hash]
25
36
  # @option option :path [String] path to configuration file
26
37
  # @option option :type [String] load type (differe by tool)
27
38
  # @option option :ignore_plugin [Boolean] set true not to load plugins
28
39
  def load_config(option = {})
29
- option = {:path => DEFAULT_CONFIG,
30
- :type => nil,
40
+ option = {:path => DEFAULT_CONFIG,
41
+ :type => nil,
31
42
  :ignore_plugin => false }.merge(option)
32
43
  conf = nil
33
44
  case File.extname(option[:path])
@@ -5,7 +5,7 @@ module Patriot
5
5
  # a configuration implementation definied by the ini-file format
6
6
  class IniFileConfig < Patriot::Util::Config::Base
7
7
 
8
- # comman section name
8
+ # common section name
9
9
  COMMON_SECTION = 'common'
10
10
 
11
11
  # @param path [String] path to a configuration file
@@ -0,0 +1,3 @@
1
+ module Patriot
2
+ VERSION = "0.8.0"
3
+ end
@@ -28,12 +28,13 @@ module Patriot
28
28
  # @abstract
29
29
  # base class for worker implementations
30
30
  class Base
31
-
31
+
32
32
  include Patriot::Util::Logger
33
33
  include Patriot::Util::Retry
34
34
  include Patriot::JobStore::Factory
35
-
35
+
36
36
  attr_accessor :host, :status, :cycle, :job_store, :config
37
+ attr_reader :started_at
37
38
 
38
39
  # @param config [Patriot::Util::Config::Base]
39
40
  def initialize(config)
@@ -73,6 +74,7 @@ module Patriot
73
74
  job_ticket.exit_code = Patriot::Command::ExitCode::SUCCEEDED
74
75
  rescue Exception => e
75
76
  @logger.warn " job : #{job_ticket.job_id} failed"
77
+ @logger.warn e
76
78
  job_ticket.description = e.to_s
77
79
  else
78
80
  job_ticket.description = command.description
@@ -122,6 +124,7 @@ module Patriot
122
124
  File.open(pid_file, 'w') {|f| f.write($$)} # save pid for shutdown
123
125
  set_traps
124
126
  @info_server.start_server
127
+ @started_at = Time.now
125
128
  @logger.info "initiating worker #{@node}@#{@host}"
126
129
  init_worker
127
130
  @status = Patriot::Worker::Status::ACTIVE
@@ -136,7 +139,7 @@ module Patriot
136
139
 
137
140
  # should be overrided in sub class
138
141
  # This method is for implementation-specific configuration
139
- def init_worker
142
+ def init_worker
140
143
  raise NotImplementedError
141
144
  end
142
145
 
@@ -1,3 +1,6 @@
1
+ require 'thin'
2
+ require 'rack/rewrite'
3
+
1
4
  module Patriot
2
5
  module Worker
3
6
  # info server (web management console and for monitoring)
@@ -8,15 +11,11 @@ module Patriot
8
11
  # default port number
9
12
  DEFAULT_PORT = '36104'
10
13
 
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
14
  # configuration key for rack handler used to start this server
17
15
  RACK_HANDLER_KEY = 'info_server.rack.handler'
18
16
  # default rack handler
19
- DEFAULT_RACK_HANDLER = 'Rack::Handler::WEBrick'
17
+ # DEFAULT_RACK_HANDLER = 'Rack::Handler::WEBrick'
18
+ DEFAULT_RACK_HANDLER = 'Rack::Handler::Thin'
20
19
 
21
20
  include Patriot::Util::Config
22
21
  include Patriot::Util::Logger
@@ -39,35 +38,41 @@ module Patriot
39
38
  @logger.info("port is not set. starting info server is skipped")
40
39
  return
41
40
  end
42
- @server_thread = Thread.new do
41
+ @server_thread = Thread.new do
43
42
  begin
44
43
  @handler = eval(@config.get(RACK_HANDLER_KEY, DEFAULT_RACK_HANDLER))
45
44
  app = Rack::URLMap.new(get_url_map)
46
45
  app = Rack::CommonLogger.new(app, build_access_logger)
47
- @handler.run app, {:Port => @port, :Host => '0.0.0.0'}
46
+ app = Rack::Rewrite.new(app){
47
+ # below 4 rules are for backward compatibility
48
+ r301 %r{^/jobs/?$}, "/"
49
+ rewrite %r{^/jobs/([^/]+)$}, "/api/v1/jobs/$1"
50
+ rewrite "/worker", "/api/v1/workers/this/state"
51
+ rewrite "/worker/status", "/api/v1/workers/this/state"
52
+ # for web console
53
+ rewrite %r{^(?!/api)}, "/"
54
+ }
55
+ app = Rack::Static.new(app, :urls => ["/js", "/css"], :root => File.join($home, "public"))
56
+ # TODO set options based on Handler type
57
+ @handler.run(app, {:Port => @port, :Host => '0.0.0.0', :signals => false}) do |server|
58
+ server.threaded = true
59
+ server.threadpool_size = 5
60
+ server.timeout = 60
61
+ end
48
62
  rescue => e
49
63
  @logger.error e
64
+ raise e
50
65
  end
51
66
  end
52
- @logger.info "info server has started"
67
+ @logger.info "info server has started with #{@handler.class}"
53
68
  return true
54
69
  end
55
70
 
56
71
  # @return [Hash<String, Sinatra::Base>]
57
72
  def get_url_map
58
- urls = @config.get(URLS_KEY, nil)
59
- if urls.nil?
60
- urlmap = {"/jobs" => Patriot::Worker::Servlet::JobServlet,
61
- "/worker" => Patriot::Worker::Servlet::WorkerStatusServlet}
62
- else
63
- urlmap = {}
64
- urls = [url] unless urls.is_a? Array
65
- urls.each do |u|
66
- servlet = eval( @config.get("#{URL_MAP_KEY_PREFIX}.#{u}") )
67
- u = "/#{u}" unless u.start_with?("/")
68
- urlmap[u] = servlet
69
- end
70
- end
73
+ urlmap = {"/" => Patriot::Worker::Servlet::IndexServlet,
74
+ "/api/v1/jobs" => Patriot::Worker::Servlet::JobAPIServlet,
75
+ "/api/v1/workers" => Patriot::Worker::Servlet::WorkerAPIServlet}
71
76
  urlmap.values.each{|servlet| servlet.configure(@worker, @config)}
72
77
  return urlmap
73
78
  end
@@ -82,8 +87,13 @@ module Patriot
82
87
  def shutdown_server
83
88
  return false if @server.nil?
84
89
  unless @server_thread.nil?
85
- @handler.shutdown
86
- @logger.info "info server shutdowned"
90
+ begin
91
+ @handler.shutdown
92
+ @logger.info "info server shutdowned"
93
+ rescue => e
94
+ @logger.error "failed to shutdown infoserver", e
95
+ raise e
96
+ end
87
97
  end
88
98
  end
89
99
 
@@ -10,14 +10,10 @@ module Patriot
10
10
  # default offset for searching jobs
11
11
  DEFAULT_JOB_OFFSET = 0
12
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
-
13
+ require 'patriot/worker/servlet/index_servlet.rb'
14
+ require 'patriot/worker/servlet/api_servlet_base.rb'
15
+ require 'patriot/worker/servlet/job_api_servlet.rb'
16
+ require 'patriot/worker/servlet/worker_api_servlet.rb'
21
17
  end
22
18
  end
23
19
  end
@@ -0,0 +1,40 @@
1
+ require 'sinatra/base'
2
+
3
+ module Patriot
4
+ module Worker
5
+ module Servlet
6
+ class APIServletBase < Sinatra::Base
7
+ set :show_exceptions, :after_handler
8
+
9
+ ### Helper Methods
10
+ helpers do
11
+ # require authorization for updating
12
+ def protected!
13
+ return if authorized?
14
+ headers['WWW-Authenticate'] = 'Basic Realm="Admin Only"'
15
+ halt 401, "Not Authorized"
16
+ end
17
+
18
+ # authorize user (basic authentication)
19
+ def authorized?
20
+ @auth ||= Rack::Auth::Basic::Request.new(request.env)
21
+ return @auth.provided? && @auth.basic? && @auth.credentials && @auth.credentials == [@@username, @@password]
22
+ end
23
+ end
24
+
25
+ # @param worker [Patriot::Wokrer::Base]
26
+ # @param config [Patriot::Util::Config::Base]
27
+ def self.configure(worker, config)
28
+ @@worker = worker
29
+ @@config = config
30
+ @@username = config.get(Patriot::Util::Config::USERNAME_KEY, "")
31
+ @@password = config.get(Patriot::Util::Config::PASSWORD_KEY, "")
32
+ end
33
+
34
+ before do
35
+ content_type 'application/json;charset=utf8'
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,32 @@
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 IndexServlet < Sinatra::Base
10
+ register Sinatra::Contrib
11
+
12
+ set :public_folder, File.join($home, "public")
13
+ set :views, File.join($home, "public", "views")
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
+ erb :index
27
+ end
28
+
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,156 @@
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 JobAPIServlet < Patriot::Worker::Servlet::APIServletBase
11
+ register Sinatra::Contrib
12
+ include Patriot::Util::DateUtil
13
+ include Patriot::Command::Parser
14
+
15
+ set :show_exceptions, :after_handler
16
+
17
+ get '/stats' do
18
+ return JSON.generate(@@worker.job_store.get_job_size(:ignore_states => [Patriot::JobStore::JobState::SUCCEEDED]))
19
+ end
20
+
21
+ get '/' do
22
+ state = (params['state'] || Patriot::JobStore::JobState::FAILED).to_i
23
+ limit = (params['limit'] || DEFAULT_JOB_LIMIT).to_i
24
+ offset = (params['offset'] || DEFAULT_JOB_OFFSET).to_i
25
+
26
+ query = {:limit => limit, :offset => offset}
27
+ query[:filter_exp] = params['filter_exp'] unless params['filter_exp'].blank?
28
+ job_ids = @@worker.job_store.find_jobs_by_state(state, query) || []
29
+ return JSON.generate(job_ids.map{|job_id| {:job_id => job_id, :state => state}})
30
+ end
31
+
32
+ get '/:job_id' do
33
+ job_id = params[:job_id]
34
+ job = @@worker.job_store.get(job_id, {:include_dependency => true})
35
+ halt(404, json({ERROR: "Job #{job_id} not found"})) if job.nil?
36
+ return JSON.generate(job.attributes.merge({'job_id' => job_id, "update_id" => job.update_id}))
37
+ end
38
+
39
+ get '/:job_id/histories' do
40
+ job_id = params[:job_id]
41
+ history_size = params['size'] || 3
42
+ histories = @@worker.job_store.get_execution_history(job_id, {:limit => history_size})
43
+ return JSON.generate(histories)
44
+ end
45
+
46
+ # get dependency graph of the specified job
47
+ #
48
+ # @param [String] job_id
49
+ # @param [String] p_level a max number of producer dependency level to get
50
+ # @param [String] c_level a max number of consumer dependency level to get
51
+ get '/:job_id/graph' do
52
+ job_id = params[:job_id]
53
+ producer_depth = (params['p_depth'] || 2).to_i
54
+ consumer_depth = (params['c_depth'] || 2).to_i
55
+ graph = @@worker.job_store.get_graph(job_id, {:producer_depth => producer_depth, :consumer_depth => consumer_depth})
56
+ return JSON.generate(graph)
57
+ end
58
+
59
+ post '/' do
60
+ protected!
61
+ body = JSON.parse(request.body.read)
62
+ halt(400, json({ERROR: "COMMAND_CLASS is not provided"})) if body["COMMAND_CLASS"].blank?
63
+ halt(400, json({ERROR: "Patriot::Command::CommandGroup is not acceptable"})) if body["COMMAND_CLASS"] == "Patriot::Command::CommandGroup"
64
+ command_class = body.delete("COMMAND_CLASS").gsub(/\./, '::').constantize
65
+
66
+ job = _build_command(command_class, body)[0]
67
+ job[Patriot::Command::STATE_ATTR] ||= body["state"]
68
+ @@worker.job_store.register(Time.now.to_i, [job])
69
+ return JSON.generate({:job_id => job.job_id})
70
+ end
71
+
72
+ put '/' do
73
+ protected!
74
+ body = JSON.parse(request.body.read)
75
+ job_ids = body["job_ids"]
76
+ state = body['state']
77
+ _set_state_of_jobs(job_ids, state)
78
+ return JSON.generate(job_ids.map{|job_id| {"job_id" => job_id, "state" => state} })
79
+ end
80
+
81
+ put '/:job_id' do
82
+ protected!
83
+ job_id = params['job_id']
84
+ job = @@worker.job_store.get(job_id)
85
+ halt(404, json({ERROR: "Job #{job_id} not found"})) if job.nil?
86
+
87
+ body = JSON.parse(request.body.read)
88
+ state = body['state']
89
+ options = body['option'] || {}
90
+ job_ids = _set_state_of_jobs(job_id, state, options)
91
+ return JSON.generate(job_ids.map{|jid| {"job_id" => jid, "state" => state}})
92
+ end
93
+
94
+ delete '/' do
95
+ protected!
96
+ body = request.body.read
97
+ job_ids = []
98
+ if body != ''
99
+ body = JSON.parse(body)
100
+ job_ids = body["job_ids"]
101
+ else
102
+ job_ids = JSON.parse(params["job_ids"])
103
+ end
104
+ job_ids.each{ |job_id| @@worker.job_store.delete_job(job_id) }
105
+ return JSON.generate(job_ids.map{|job_id| {"job_id" => job_id} })
106
+ end
107
+
108
+ delete '/:job_id' do
109
+ protected!
110
+ job_id = params['job_id']
111
+ job = @@worker.job_store.get(job_id)
112
+ halt(404, json({ERROR: "Job #{job_id} not found"})) if job.nil?
113
+
114
+ @@worker.job_store.delete_job(job_id)
115
+ return JSON.generate({"job_id" => job_id})
116
+ end
117
+
118
+ error JobNotFoundException do
119
+ "Job #{env['sinatra.error'].message} is not found"
120
+ end
121
+
122
+ # @private
123
+ def _set_state_of_jobs(job_ids, state, opts = {})
124
+ job_ids = [job_ids] unless job_ids.is_a? Array
125
+ opts = {'with_subsequent' => false}.merge(opts)
126
+ opts = {:include_subsequent => false}.merge(opts)
127
+ update_id = Time.now.to_i
128
+ @@worker.job_store.set_state(update_id, job_ids, state)
129
+ if opts['with_subsequent']
130
+ @@worker.job_store.process_subsequent(job_ids) do |job_store, jobs|
131
+ next if jobs.empty?
132
+ subsequent_ids = jobs.map(&:job_id)
133
+ @@worker.job_store.set_state(update_id, subsequent_ids, state)
134
+ job_ids |= subsequent_ids
135
+ end
136
+ end
137
+ return job_ids.uniq
138
+ end
139
+ private :_set_state_of_jobs
140
+
141
+ # @private
142
+ def _build_command(clazz, params)
143
+ _params = params.dup
144
+ _params["target_datetime"] = Date.today
145
+ cmd = clazz.new(@@config)
146
+ cmd.produce(_params.delete("products")) unless _params["products"].nil?
147
+ cmd.require(_params.delete("requisites")) unless _params["requisites"].nil?
148
+ cmds = cmd.build(_params)
149
+ return cmds.map{|c| c.to_job}
150
+ end
151
+ private :_build_command
152
+
153
+ end
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,67 @@
1
+ require 'sinatra/base'
2
+ require 'sinatra/contrib'
3
+
4
+ module Patriot
5
+ module Worker
6
+ module Servlet
7
+ # excepton thrown when a woker is not accessible
8
+ class WorkerInaccessibleException < Exception; end
9
+ # provide worker management functionalities
10
+ class WorkerAPIServlet < Patriot::Worker::Servlet::APIServletBase
11
+ register Sinatra::Contrib
12
+
13
+ HOST_KEY = "host"
14
+ VERSION_KEY = "version"
15
+ CLASS_KEY = "class"
16
+ STARTED_AT_KEY = "started_at"
17
+ CONFIG_KEY = "config"
18
+
19
+ STATE_KEY = "state"
20
+
21
+ LOCALHOST_EXPS = ["localhost", "127.0.0.1"]
22
+
23
+ set :show_exceptions, :after_handler
24
+
25
+ get '/' do
26
+ worker_hosts = @@config.get(Patriot::Util::Config::WORKER_HOST_KEY)
27
+ worker_hosts = [worker_hosts] unless worker_hosts.is_a?(Array)
28
+ worker_hosts = @@config.get(Patriot::Util::Config::WORKER_HOST_KEY).map do |h|
29
+ h = h.split(":")
30
+ port = h.size == 2 ? h[1] : Patriot::Worker::InfoServer::DEFAULT_PORT
31
+ {'host' => h[0], 'port' => port}
32
+ end
33
+ return JSON.generate(worker_hosts)
34
+ end
35
+
36
+ get '/this' do
37
+ return JSON.generate(
38
+ STATE_KEY => @@worker.status,
39
+ HOST_KEY => @@worker.host,
40
+ VERSION_KEY => Patriot::VERSION,
41
+ CLASS_KEY => @@worker.class.to_s,
42
+ STARTED_AT_KEY => @@worker.started_at,
43
+ CONFIG_KEY => {} # TODO
44
+ )
45
+ end
46
+
47
+ get '/this/state' do
48
+ return JSON.generate(
49
+ STATE_KEY => @@worker.status
50
+ )
51
+ end
52
+
53
+ put '/this/state' do
54
+ protected!
55
+ new_status = params['status']
56
+ if [Patriot::Worker::Status::ACTIVE, Patriot::Worker::Status::SLEEP ].include?(@@worker.status)
57
+ @@worker.status = new_status
58
+ else
59
+ # state cannot be changed in shutdown process
60
+ halt 403
61
+ end
62
+ end
63
+
64
+ end
65
+ end
66
+ end
67
+ end