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
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- NGE3Nzk3MmFkMDI1YTQxZWU4OTAxMjU3MmNlMDM0NTU5M2E2MzI4Zg==
4
+ YjNjNzA3NWI2YWU2ZmQ0OWZhM2E1ZmJjNTVlNTBmN2Q0YzkyYjQ3OQ==
5
5
  data.tar.gz: !binary |-
6
- MzQzZTkxM2UzODU3NmIyODFjZWFhZjJiOWRjZTk1OWI2NTJiY2ZjNg==
6
+ MDRmNGNkOWE0MDAzYmY2NDA5MGZkYzJhNDc2N2Y0ZWM4ZjEzMGM2OQ==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- YTY0M2ZlZGVjZDg5YmQ1M2UzOTcxNTNkMGQ0YzA5OTMxOGQwYjU1YzliYmEz
10
- MGY0MGFiNDk1NDNlOGRiZDM5MWIzZDk2ODJkYzBlODdiZWU0ZTZiNTU1Mjk4
11
- NzcyMTZmZGRmZWVjNzMyN2ZmNmQyOWJjN2ZjZGFjNmQzMmNkNTU=
9
+ ZTY5YzAwNjFmYjNlNjllODliNGM4MzRjMjBhYzgwMDZhZWZlZjcyYWE2MmM4
10
+ ZGYwNDgyNTljOTg5ZTY0MDBmN2M4ZWYyOWQ4YTE5YzkwM2Y2YmE4NGU3NWI5
11
+ MDdkNDIwMWFmNWYzZjY3MzIzZmMzNmQ0OWQ2MTc2NWRkNWJmZDM=
12
12
  data.tar.gz: !binary |-
13
- YThmNjgyYzhjNjk5YzRjZmM3NjEyYTRmMWM4NWRlOWRiZDA3MzE4MmQ1MzI1
14
- ZTk2ZDY3M2E0YmM4ZmNlMDRkNGZmMGNlMDcyZDUyYWY3MjY4Mjc1YjczYTMw
15
- Njk5ZTVlMzI2M2NjMDBlM2ExM2ZiZTg0NzkzY2ZkNTY2NGY5NTk=
13
+ NWE5OGY0NDQ2NGY4NjAwNTJmMjIwNGJiMWU5OGExMjY4ZjkxMTQyOGU2MjYy
14
+ Mjg5YzgxZDVhOTZlZGQzYTg1YWU3ZDYyY2Y4N2FiYzk4NmY1ODQ0MDdlYmVm
15
+ MWU0MWE1ZTNmY2UyYjE0NWM5Y2M0MWVhMjRjOGZiMzQ2YTFhOTI=
@@ -2,6 +2,7 @@
2
2
  require 'rubygems'
3
3
  require 'active_support'
4
4
 
5
+ require 'patriot/version'
5
6
  require 'patriot/util'
6
7
  require 'patriot/command'
7
8
  require 'patriot/job_store'
@@ -1,28 +1,28 @@
1
1
  # the root name space for this scheduler
2
2
  module Patriot
3
3
  # a name space for commands
4
- module Command
4
+ module Command
5
5
  # a parameter key for command class name (used in jobs)
6
- COMMAND_CLASS_KEY = "COMMAND_CLASS"
6
+ COMMAND_CLASS_KEY = :COMMAND_CLASS
7
7
 
8
8
  # attribute name for required products
9
- REQUISITES_ATTR = "requisites"
9
+ REQUISITES_ATTR = :requisites
10
10
  # attribute name for produced products
11
- PRODUCTS_ATTR = "products"
11
+ PRODUCTS_ATTR = :products
12
12
  # attribute name for job state
13
- STATE_ATTR = "state"
13
+ STATE_ATTR = :state
14
14
  # attribute name for job priority
15
- PRIORITY_ATTR = "priority"
15
+ PRIORITY_ATTR = :priority
16
16
  # attribute name for job execution node constraint
17
- EXEC_NODE_ATTR = "exec_node"
17
+ EXEC_NODE_ATTR = :exec_node
18
18
  # attribute name for job execution host constraint
19
- EXEC_HOST_ATTR = "exec_host"
19
+ EXEC_HOST_ATTR = :exec_host
20
20
  # attribute name for start time constraint
21
- START_DATETIME_ATTR = "start_datetime"
21
+ START_DATETIME_ATTR = :start_datetime
22
22
  # attribute name for retry configration
23
- POST_PROCESSORS_ATTR = "post_processors"
23
+ POST_PROCESSORS_ATTR = :post_processors
24
24
 
25
- # a list of comman attributes
25
+ # a list of command attributes
26
26
  COMMON_ATTRIBUTES = [
27
27
  REQUISITES_ATTR,
28
28
  PRODUCTS_ATTR,
@@ -16,7 +16,7 @@ module Patriot
16
16
  attr_accessor :config, :parser, :test_mode, :target_datetime, :post_processors
17
17
  attr_writer :start_datetime
18
18
 
19
- # comman attributes handled distinctively (only effective in top level commands)
19
+ # command attributes handled distinctively (only effective in top level commands)
20
20
  volatile_attr :requisites,
21
21
  :products,
22
22
  :priority,
@@ -145,7 +145,7 @@ module Patriot
145
145
  def init_param
146
146
  # set parameter value to instance variable
147
147
  @param.each do |k,v|
148
- raise "a reserved word #{k} is used as parameter name" if Patriot::Command::COMMAND_CLASS_KEY == k
148
+ raise "a reserved word #{k} is used as parameter name" if Patriot::Command::COMMAND_CLASS_KEY == k.to_sym
149
149
  raise "#{k} is already used in #{self.job_id}" unless instance_variable_get("@#{k}".to_sym).nil?
150
150
  # don't evaluate here since all parameters are not set to instance variables
151
151
  instance_variable_set("@#{k}".to_sym,v)
@@ -177,7 +177,7 @@ module Patriot
177
177
  val = self.instance_variable_get("@#{attr}".to_sym)
178
178
  logics.each do |l|
179
179
  unless l.call(self, attr, val)
180
- raise "validation error : #{attr}=#{val} (#{self.class})"
180
+ raise "validation error : #{attr}=#{val} (#{self.class})"
181
181
  end
182
182
  end
183
183
  end
@@ -1,11 +1,11 @@
1
1
  module Patriot
2
2
  module Command
3
3
  # a command which is composed of multiple sub commands
4
- class CompositeCommand < Patriot::Command::CommandGroup
5
- declare_command_name :composite_command
6
- declare_command_name :composite_job
4
+ class CompositeCommand < Patriot::Command::CommandGroup
5
+ declare_command_name :composite_command
6
+ declare_command_name :composite_job
7
7
  private_command_attr :contained_commands => []
8
- volatile_attr :name, :name_suffix
8
+ command_attr :name, :name_suffix
9
9
 
10
10
  # @return [String] the identifier of this composite command
11
11
  # @see Patriot::Command::Base#job_id
@@ -27,7 +27,9 @@ module Patriot
27
27
  @name_suffix ||= _date_
28
28
  # don't do flatten to handle nested composite commands
29
29
  @subcommands.map do |cmd|
30
- cmd.build(@param).each do |cmd|
30
+ cmd = cmd.clone
31
+ cmd.build(@param).each do |cmd|
32
+ _validate_command(cmd)
31
33
  require cmd['requisites']
32
34
  produce cmd['products']
33
35
  @contained_commands << cmd
@@ -42,7 +44,19 @@ module Patriot
42
44
  @contained_commands.each do |c|
43
45
  c.execute
44
46
  end
45
- end
47
+ end
48
+
49
+ # @private
50
+ # validate command
51
+ # @param [Patriot::Command::Base] cmd
52
+ def _validate_command(cmd)
53
+ if !cmd['post_processors'].nil?
54
+ raise 'you cannot set "post_processor" at subcommand of composite_job\'s ' \
55
+ + "\n" + 'name: ' + cmd['name'] \
56
+ + "\n" + 'command: ' + cmd['commands'].to_s
57
+ end
58
+ end
59
+ private :_validate_command
46
60
 
47
61
  end
48
62
  end
@@ -1,9 +1,9 @@
1
1
  # the root name space for this scheduler
2
2
  module Patriot
3
3
  # a name space for commands
4
- module Command
4
+ module Command
5
5
  module PostProcessor
6
- POST_PROCESSOR_CLASS_KEY = "POST_PROCESSOR_CLASS"
6
+ POST_PROCESSOR_CLASS_KEY = :POST_PROCESSOR_CLASS
7
7
  require 'patriot/command/post_processor/base'
8
8
  require 'patriot/command/post_processor/skip_on_fail'
9
9
  require 'patriot/command/post_processor/retrial'
@@ -22,8 +22,9 @@ module Patriot
22
22
 
23
23
  # @param props [Hash] properties of this post processor
24
24
  def initialize(props = {})
25
- validate_props(props)
26
- @props = props
25
+ @props = {}
26
+ props.each{|k,v| @props[k.to_sym] = v}
27
+ validate_props(@props)
27
28
  end
28
29
 
29
30
  def validate_props(props)
@@ -4,10 +4,10 @@ module Patriot
4
4
  module PostProcessor
5
5
  class MailNotification < Patriot::Command::PostProcessor::Base
6
6
 
7
- TO_PROP_KEY = 'to'
8
- ON_PROP_KEY = 'on'
7
+ TO_PROP_KEY = :to
8
+ ON_PROP_KEY = :on
9
9
 
10
- declare_post_processor_name :mail_notification
10
+ declare_post_processor_name :mail_notification
11
11
 
12
12
  def validate_props(props)
13
13
  raise "#{TO_PROP_KEY} is not specified" unless props.has_key?(TO_PROP_KEY)
@@ -3,8 +3,8 @@ module Patriot
3
3
  module PostProcessor
4
4
  class Retrial < Patriot::Command::PostProcessor::Base
5
5
 
6
- COUNT_PROP_KEY = 'count'
7
- INTERVAL_PROP_KEY = 'interval'
6
+ COUNT_PROP_KEY = :count
7
+ INTERVAL_PROP_KEY = :interval
8
8
 
9
9
  declare_post_processor_name :retrial
10
10
 
@@ -21,7 +21,7 @@ module Patriot
21
21
  found = true
22
22
  # count first attempt in
23
23
  pp.props[COUNT_PROP_KEY] = pp.props[COUNT_PROP_KEY] - 1
24
- return if pp.props[COUNT_PROP_KEY] == 0
24
+ return if pp.props[COUNT_PROP_KEY] == 0
25
25
  cmd.start_datetime = Time.now + pp.props[INTERVAL_PROP_KEY]
26
26
  end
27
27
  job = cmd.to_job
@@ -1,15 +1,29 @@
1
1
  module Patriot
2
2
  module Command
3
3
  # a command which executes shell scripts
4
+ #
5
+ # == Example pbc
6
+ # sh {
7
+ # name "test"
8
+ # commands "echo '#{_date_}' > /tmp/test.out"
9
+ # }
4
10
  class ShCommand < Patriot::Command::Base
5
11
  include Patriot::Util::System
6
12
 
7
13
  declare_command_name :sh
8
14
 
9
15
  command_attr :connector => '&&'
16
+ # @!attribute [w] commands
17
+ # [String, Array] commands to execute
10
18
  command_attr :commands do |cmd, a, v|
11
19
  cmd.commands = v.is_a?(Array)? v : [v]
12
20
  end
21
+ # @!attribute [w] name
22
+ # [String] string to be a part of job id
23
+ # @see Patriot::Command::ShCommand#job_id
24
+ # @!attribute [w] name_suffix
25
+ # [String] suffix string to be a part of job id
26
+ # @see Patriot::Command::ShCommand#job_id
13
27
  command_attr :name, :name_suffix
14
28
  validate_existence :name
15
29
 
@@ -1,4 +1,5 @@
1
1
  require 'rest_client'
2
+ require 'base64'
2
3
 
3
4
  module Patriot
4
5
  module Controller
@@ -18,21 +19,24 @@ module Patriot
18
19
  @config = config
19
20
  @logger = create_logger(config)
20
21
  set_default_values
22
+ username = config.get(Patriot::Util::Config::USERNAME_KEY, "")
23
+ password = config.get(Patriot::Util::Config::PASSWORD_KEY, "")
24
+ @auth = 'Basic ' + Base64.encode64("#{username}:#{password}").chomp
21
25
  end
22
26
 
23
27
  # @private
24
28
  def set_default_values
25
- @default_hosts = @config.get('worker_hosts') || []
26
- @default_port = @config.get('info_server_port')
27
- @user = @config.get('admin_user')
29
+ @default_hosts = @config.get(WORKER_HOST_KEY) || []
30
+ @default_port = @config.get(INFO_SERVER_PORT_KEY)
31
+ @user = @config.get(ADMIN_USER_KEY)
28
32
  end
29
33
  private :set_default_values
30
34
 
31
35
  # execute block for each target hosts
32
36
  # @param options [Hash]
33
37
  # @option options :host a target host
34
- # @option options :hosts a comman separated value of target hosts
35
- # @option options :all set true to target all hosts in the configuration
38
+ # @option options :hosts a comma separated value of target hosts
39
+ # @option options :all set true to target all hosts in the configuration
36
40
  # @return [Hash] a hash from host name to the result of the block
37
41
  def request_to_target_hosts(options = {}, &blk)
38
42
  hosts = []
@@ -90,7 +94,8 @@ module Patriot
90
94
  # @param host [String] host name of the target host
91
95
  # @param port [String] port number of the worker process on the target host
92
96
  def put_worker_status(host, port, new_status)
93
- return RestClient.put("http://#{host}:#{port}/worker", :status => new_status)
97
+ resource = RestClient::Resource.new("http://#{host}:#{port}/worker")
98
+ return resource.put({:status => new_status}, :Authorization => @auth )
94
99
  end
95
100
 
96
101
  # start target workers
@@ -109,13 +114,13 @@ module Patriot
109
114
  # @param options @see {#request_to_target_hosts}
110
115
  def restart_worker(options = {})
111
116
  options = {:interval => 60}.merge(options)
112
- target_nodes = request_to_target_hosts(options){|h,p| controll_worker_at(h,'stop')}
117
+ target_nodes = request_to_target_hosts(options){|h,p| controll_worker_at(h,'stop')}
113
118
  target_nodes.keys.each{|host| target_nodes[host] = true}
114
119
 
115
120
  port = options.has_key?(:port) ? options[:port] : @default_port
116
121
  while(target_nodes.has_value?(true))
117
122
  target_nodes.keys.each do |host|
118
- next unless target_nodes[host] # skip already started
123
+ next unless target_nodes[host] # skip already started
119
124
  res = get_worker_status(host,port)
120
125
  if res.nil?
121
126
  controll_worker_at(host,'start')
@@ -63,17 +63,18 @@ module Patriot
63
63
  end
64
64
 
65
65
  # get a job
66
- # @param [String] job_id
67
- # @param [Hash] opts
66
+ # @param job_id [String] job_id
67
+ # @param opts [Hash]
68
68
  # @option opts [String] :include_dependency include jobs with 1-hop dependency
69
69
  # @return [Patrio::JobStore::Job] in case of include_dependency is true,
70
70
  # jobs in dependency set to :consumers/:producers as a hash from job_id to state
71
71
  def get(job_id, opts={})
72
+ return nil if job_id.nil?
72
73
  job = get_job(job_id)
73
74
  return if job.nil?
74
75
  if opts[:include_dependency] == true
75
- job['consumers'] = get_consumers(job[Patriot::Command::PRODUCTS_ATTR]) || {}
76
- job['producers'] = get_producers(job[Patriot::Command::REQUISITES_ATTR]) ||{}
76
+ job[:consumers] = get_consumers(job[Patriot::Command::PRODUCTS_ATTR]) || []
77
+ job[:producers] = get_producers(job[Patriot::Command::REQUISITES_ATTR]) || []
77
78
  end
78
79
  return job
79
80
  end
@@ -93,7 +94,7 @@ module Patriot
93
94
  def get_producers(products, opts = {:include_attrs => [Patriot::Command::STATE_ATTR]})
94
95
  raise NotImplementedError
95
96
  end
96
-
97
+
97
98
  # get consumers of products
98
99
  # @param [Array] products a list of product name
99
100
  # @param [Hash] opts
@@ -112,6 +113,14 @@ module Patriot
112
113
  raise NotImplementedError
113
114
  end
114
115
 
116
+ # get nodes and edges information to render graph
117
+ # @param [String] job_id JOB ID
118
+ # @param [Hash] opts options
119
+ # @return [Array] [nodes, edges]
120
+ def get_graph(job_id, opts = {})
121
+ raise NotImplementedError
122
+ end
123
+
115
124
  # @param [Patriot::JobStore::JobState] state
116
125
  # @param [Hash] opts
117
126
  # @option ops [Integer] :limit a max nubmer of jobs fetched at once
@@ -124,7 +133,7 @@ module Patriot
124
133
 
125
134
  # @param [Hash] opts
126
135
  # @option [Array<Patriot::JobStore::JobState>] :ignore_states
127
- # @return [Hash<Patriot::JobStore::JobState, Integer>] a hash from job state to the number of jobs in the state
136
+ # @return [Hash<Patriot::JobStore::JobState, Integer>] a hash from job state to the number of jobs in the state
128
137
  def get_job_size(opts = {})
129
138
  raise NotImplementedError
130
139
  end
@@ -147,7 +156,7 @@ module Patriot
147
156
  }.compact.flatten
148
157
  consumers = get_consumers(products)
149
158
  while !consumers.empty?
150
- jobs = consumers.keys.map{|jid| get_job(jid)}.compact
159
+ jobs = consumers.map{|job| get_job(job[:job_id])}.compact
151
160
  yield self, jobs
152
161
  products = jobs.map{|j| j[Patriot::Command::PRODUCTS_ATTR]}.compact.flatten
153
162
  consumers = get_consumers(products)
@@ -29,26 +29,31 @@ module Patriot
29
29
  def register(update_id, jobs)
30
30
  jobs.each{|job| raise "#{job.job_id} is not acceptable" unless acceptable?(job) }
31
31
  @mutex.synchronize do
32
- jobs.each do |job|
33
- job_id = job.job_id.to_sym
34
- job.update_id = update_id
35
- if @jobs.has_key?(job_id) # update
36
- job[Patriot::Command::STATE_ATTR] ||= @jobs[job_id][Patriot::Command::STATE_ATTR]
37
- else # insert
38
- # set default state
39
- job[Patriot::Command::STATE_ATTR] ||= Patriot::JobStore::JobState::INIT
40
- end
41
- @jobs[job_id] = job
42
- @producers[job_id] = job[Patriot::Command::PRODUCTS_ATTR] || []
43
- @consumers[job_id] = job[Patriot::Command::REQUISITES_ATTR] || []
44
- if job[Patriot::Command::STATE_ATTR] == Patriot::JobStore::JobState::INIT
45
- _set_state(job_id, Patriot::JobStore::JobState::WAIT)
46
- else
47
- _set_state(job_id, job[Patriot::Command::STATE_ATTR])
48
- end
49
- end
32
+ jobs.each {|job| _upsert(update_id, job) }
33
+ end
34
+ end
35
+
36
+ def _upsert(update_id, job)
37
+ job_id = job.job_id.to_sym
38
+ if @jobs.has_key?(job_id) # update
39
+ original = @jobs[job_id]
40
+ job[Patriot::Command::STATE_ATTR] ||= original[Patriot::Command::STATE_ATTR]
41
+ job.update_id = original.update_id
42
+ else # insert
43
+ job[Patriot::Command::STATE_ATTR] ||= Patriot::JobStore::JobState::INIT
44
+ raise "update_id id should not be nil for new jobs" if update_id.nil?
45
+ job.update_id = update_id
46
+ end
47
+ @jobs[job_id] = job
48
+ @producers[job_id] = job[Patriot::Command::PRODUCTS_ATTR] || []
49
+ @consumers[job_id] = job[Patriot::Command::REQUISITES_ATTR] || []
50
+ if job[Patriot::Command::STATE_ATTR] == Patriot::JobStore::JobState::INIT
51
+ _set_state(job_id, Patriot::JobStore::JobState::WAIT)
52
+ else
53
+ _set_state(job_id, job[Patriot::Command::STATE_ATTR])
50
54
  end
51
55
  end
56
+ private :_upsert
52
57
 
53
58
  # @see Patriot::JobStore::Base#acceptable?
54
59
  def acceptable?(job)
@@ -141,6 +146,8 @@ module Patriot
141
146
 
142
147
  # @see Patriot::JobStore::Base#get_job
143
148
  def get_job(job_id)
149
+ return nil if job_id.nil?
150
+ raise "string is expected but job_id is a #{job_id.class}" unless job_id.is_a?(String)
144
151
  return @jobs[job_id.to_sym]
145
152
  end
146
153
 
@@ -148,26 +155,34 @@ module Patriot
148
155
  def get_producers(products, opts = {:include_attrs => [Patriot::Command::STATE_ATTR]})
149
156
  opts = {:include_attrs => []}.merge(opts)
150
157
  products = [products] unless products.is_a?(Array)
151
- producers = {}
152
- products.each{|product|
153
- @producers.map{|pid, prods|
154
- producers[pid.to_s] = @jobs[pid].filter_attributes(opts[:include_attrs]) if prods.include?(product)
158
+ producers = []
159
+ products.each{|product|
160
+ @producers.map{|pid, prods|
161
+ if prods.include?(product)
162
+ job = @jobs[pid].filter_attributes(opts[:include_attrs])
163
+ job[:job_id] = pid.to_s
164
+ producers.push(job)
165
+ end
155
166
  }
156
167
  }
157
- return producers
168
+ return producers.uniq
158
169
  end
159
-
170
+
160
171
  # @see Patriot::JobStore::Base#get_consumers
161
172
  def get_consumers(products, opts = {:include_attrs => [Patriot::Command::STATE_ATTR]})
162
173
  opts = {:include_attrs => []}.merge(opts)
163
174
  products = [products] unless products.is_a?(Array)
164
- consumers = {}
175
+ consumers = []
165
176
  products.each{|product|
166
- @consumers.map{|pid, prods|
167
- consumers[pid.to_s] = @jobs[pid].filter_attributes(opts[:include_attrs]) if prods.include?(product)
177
+ @consumers.map{|pid, prods|
178
+ if prods.include?(product)
179
+ job = @jobs[pid].filter_attributes(opts[:include_attrs])
180
+ job[:job_id] = pid.to_s
181
+ consumers.push(job)
182
+ end
168
183
  }
169
184
  }
170
- return consumers
185
+ return consumers.uniq
171
186
  end
172
187
 
173
188
  # @see Patriot::JobStore::Base#find_jobs_by_state
@@ -193,6 +208,83 @@ module Patriot
193
208
  return @job_history[job_id.to_sym] || []
194
209
  end
195
210
 
211
+ # get nodes and edges information to render graph
212
+ # @param [String] job_id JOB ID
213
+ # @param [Hash] opts options
214
+ # @return [Array] [nodes, edges]
215
+ def get_graph(job_id, opts = {})
216
+ job = get(job_id)
217
+ history = get_execution_history(job_id, {})[0]
218
+
219
+ hashed_job = {
220
+ :job_id => job.job_id,
221
+ :history => history,
222
+ :depth => 0
223
+ }.merge(job.attributes)
224
+
225
+ # set self node
226
+ nodes = {job_id => hashed_job}
227
+ edges = []
228
+
229
+ _set_dependency(
230
+ :producers,
231
+ opts[:producer_depth],
232
+ nodes,
233
+ edges,
234
+ hashed_job
235
+ )
236
+
237
+ _set_dependency(
238
+ :consumers,
239
+ opts[:consumer_depth],
240
+ nodes,
241
+ edges,
242
+ hashed_job
243
+ )
244
+
245
+ return {:nodes => nodes, :edges => edges}
246
+ end
247
+
248
+ # get dependency and set nodes and edges
249
+ #
250
+ # @private
251
+ # @param [Symbol] direction :producers or :consumers
252
+ # @param [Integer] depth dependency depth to get
253
+ # @param [Hash] nodes nodes to set for dager-d3
254
+ # @param [Array] edges edges to set for dager-d3
255
+ # @param [Hash] base_job base job to get dependency
256
+ def _set_dependency(direction, depth, nodes, edges, base_job)
257
+ return if nodes[base_job[:job_id]][:depth] == depth
258
+
259
+ base_job[direction].map{|depend_job|
260
+ job = get(depend_job[:job_id])
261
+ history = get_execution_history(depend_job[:job_id], {})[0]
262
+
263
+ hashed_job = {
264
+ :job_id => job.job_id,
265
+ :history => history,
266
+ :depth => base_job[:depth] + 1
267
+ }.merge(job.attributes)
268
+
269
+ nodes[job.job_id] = hashed_job
270
+ if direction == :producers
271
+ edges.push([job.job_id, base_job[:job_id]])
272
+ else
273
+ edges.push([base_job[:job_id], job.job_id])
274
+ end
275
+
276
+ # call recursively
277
+ _set_dependency(
278
+ direction,
279
+ depth,
280
+ nodes,
281
+ edges,
282
+ hashed_job
283
+ )
284
+ }
285
+ end
286
+ private :_set_dependency
287
+
196
288
  # @see Patriot::JobStore::Base#get_job_size
197
289
  def get_job_size(opts = {})
198
290
  opts = {:ignore_states => []}.merge(opts)
@@ -208,7 +300,7 @@ module Patriot
208
300
  # @see Patriot::JobStore::Base#delete_job
209
301
  def delete_job(job_id)
210
302
  job_id = job_id.to_sym
211
- @mutex.synchronize do
303
+ @mutex.synchronize do
212
304
  @job_states.each{|s,js| js.delete(job_id)}
213
305
  @jobs.delete(job_id)
214
306
  @producers.delete(job_id)