tengine_job 0.6.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (133) hide show
  1. data/.document +5 -0
  2. data/.rspec +1 -0
  3. data/Gemfile +23 -0
  4. data/Gemfile.lock +109 -0
  5. data/README.rdoc +20 -0
  6. data/Rakefile +42 -0
  7. data/VERSION +1 -0
  8. data/examples/0004_retry_one_layer.rb +24 -0
  9. data/examples/0004_retry_one_layer.sh +38 -0
  10. data/examples/0005_retry_two_layer.rb +54 -0
  11. data/examples/0005_retry_two_layer.sh +80 -0
  12. data/examples/0006_retry_three_layer.rb +58 -0
  13. data/examples/0006_retry_three_layer.sh +74 -0
  14. data/examples/0007_simple_jobnet.rb +7 -0
  15. data/examples/0021_dynamic_env.rb +20 -0
  16. data/examples/VERSION +1 -0
  17. data/examples/tengine_job_test.sh +10 -0
  18. data/lib/tengine/job.rb +94 -0
  19. data/lib/tengine/job/category.rb +54 -0
  20. data/lib/tengine/job/connectable.rb +43 -0
  21. data/lib/tengine/job/drivers/job_control_driver.rb +82 -0
  22. data/lib/tengine/job/drivers/job_execution_driver.rb +30 -0
  23. data/lib/tengine/job/drivers/jobnet_control_driver.rb +117 -0
  24. data/lib/tengine/job/drivers/schedule_driver.rb +30 -0
  25. data/lib/tengine/job/dsl_binder.rb +12 -0
  26. data/lib/tengine/job/dsl_evaluator.rb +18 -0
  27. data/lib/tengine/job/dsl_loader.rb +180 -0
  28. data/lib/tengine/job/edge.rb +150 -0
  29. data/lib/tengine/job/element_selector_notation.rb +169 -0
  30. data/lib/tengine/job/end.rb +32 -0
  31. data/lib/tengine/job/executable.rb +74 -0
  32. data/lib/tengine/job/execution.rb +141 -0
  33. data/lib/tengine/job/expansion.rb +37 -0
  34. data/lib/tengine/job/fork.rb +6 -0
  35. data/lib/tengine/job/job.rb +23 -0
  36. data/lib/tengine/job/jobnet.rb +173 -0
  37. data/lib/tengine/job/jobnet/builder.rb +150 -0
  38. data/lib/tengine/job/jobnet/job_state_transition.rb +167 -0
  39. data/lib/tengine/job/jobnet/jobnet_state_transition.rb +110 -0
  40. data/lib/tengine/job/jobnet/state_transition.rb +37 -0
  41. data/lib/tengine/job/jobnet_actual.rb +55 -0
  42. data/lib/tengine/job/jobnet_template.rb +10 -0
  43. data/lib/tengine/job/join.rb +6 -0
  44. data/lib/tengine/job/junction.rb +29 -0
  45. data/lib/tengine/job/killing.rb +30 -0
  46. data/lib/tengine/job/mm_compatibility.rb +6 -0
  47. data/lib/tengine/job/mm_compatibility/connectable.rb +13 -0
  48. data/lib/tengine/job/name_path.rb +31 -0
  49. data/lib/tengine/job/root.rb +16 -0
  50. data/lib/tengine/job/root_jobnet_actual.rb +39 -0
  51. data/lib/tengine/job/root_jobnet_template.rb +49 -0
  52. data/lib/tengine/job/script_executable.rb +235 -0
  53. data/lib/tengine/job/signal.rb +121 -0
  54. data/lib/tengine/job/start.rb +20 -0
  55. data/lib/tengine/job/stoppable.rb +15 -0
  56. data/lib/tengine/job/vertex.rb +172 -0
  57. data/lib/tengine_job.rb +3 -0
  58. data/spec/fixtures/rjn_0001_simple_jobnet_builder.rb +42 -0
  59. data/spec/fixtures/rjn_0002_simple_parallel_jobnet_builder.rb +42 -0
  60. data/spec/fixtures/rjn_0003_fork_join_jobnet_builder.rb +61 -0
  61. data/spec/fixtures/rjn_0004_parallel_jobnet_with_finally_fixture.rb +62 -0
  62. data/spec/fixtures/rjn_0005_retry_two_layer_fixture.rb +153 -0
  63. data/spec/fixtures/rjn_0008_expansion_fixture.rb +32 -0
  64. data/spec/fixtures/rjn_0009_tree_sequential_jobnet_builder.rb +174 -0
  65. data/spec/fixtures/rjn_0010_2jobs_and_1job_parallel_jobnet_builder.rb +39 -0
  66. data/spec/fixtures/rjn_0011_nested_fork_jobnet_builder.rb +96 -0
  67. data/spec/fixtures/rjn_0012_nested_and_finally_builder.rb +157 -0
  68. data/spec/fixtures/rjn_1004_hadoop_job_in_jobnet_fixture.rb +105 -0
  69. data/spec/fixtures/rjn_means_root_jobnet +0 -0
  70. data/spec/fixtures/test_credential_fixture.rb +12 -0
  71. data/spec/fixtures/test_server_fixture.rb +28 -0
  72. data/spec/mongoid.yml +35 -0
  73. data/spec/spec_helper.rb +56 -0
  74. data/spec/sshd/.gitignore +1 -0
  75. data/spec/sshd/id_rsa +51 -0
  76. data/spec/sshd/id_rsa.pub +1 -0
  77. data/spec/sshd/ssh_host_rsa_key +51 -0
  78. data/spec/sshd/ssh_host_rsa_key.pub +1 -0
  79. data/spec/sshd/sshd_config +10 -0
  80. data/spec/sshd/sshd_config.erb +11 -0
  81. data/spec/sshd/tengine_job_test.sh +6 -0
  82. data/spec/support/jobnet_fixture_builder.rb +145 -0
  83. data/spec/support/mongo_index_key_log.rb +91 -0
  84. data/spec/tengine/job/category_spec.rb +193 -0
  85. data/spec/tengine/job/connectable_spec.rb +94 -0
  86. data/spec/tengine/job/drivers/job_controll_driver/connection_error_spec.rb +236 -0
  87. data/spec/tengine/job/drivers/job_controll_driver/duplicated_job_start_spec.rb +302 -0
  88. data/spec/tengine/job/drivers/job_controll_driver/expansion_spec.rb +120 -0
  89. data/spec/tengine/job/drivers/job_controll_driver/stop_spec.rb +159 -0
  90. data/spec/tengine/job/drivers/job_controll_driver_spec.rb +623 -0
  91. data/spec/tengine/job/drivers/job_execution_driver_spec.rb +88 -0
  92. data/spec/tengine/job/drivers/jobnet_control_driver/nested_and_finally_spec.rb +472 -0
  93. data/spec/tengine/job/drivers/jobnet_control_driver/nested_jobnet_spec.rb +231 -0
  94. data/spec/tengine/job/drivers/jobnet_control_driver/stop_jobnet_spec.rb +202 -0
  95. data/spec/tengine/job/drivers/jobnet_control_driver_spec.rb +446 -0
  96. data/spec/tengine/job/drivers/schedule_driver_spec.rb +202 -0
  97. data/spec/tengine/job/dsl_binder_spec.rb +36 -0
  98. data/spec/tengine/job/dsl_loader_spec.rb +403 -0
  99. data/spec/tengine/job/dsls/0013_hadoop_job_run.rb +29 -0
  100. data/spec/tengine/job/dsls/0014_join_and_join.rb +19 -0
  101. data/spec/tengine/job/dsls/0015_fork_and_fork.rb +18 -0
  102. data/spec/tengine/job/dsls/0016_complex_fork_and_join.rb +20 -0
  103. data/spec/tengine/job/dsls/0017_finally.rb +15 -0
  104. data/spec/tengine/job/dsls/0018_expansion.rb +23 -0
  105. data/spec/tengine/job/dsls/0019_execute_job_on_event.rb +16 -0
  106. data/spec/tengine/job/dsls/0020_duplicated_jobnet_name.rb +16 -0
  107. data/spec/tengine/job/dsls/1060_test_dir1/1060_test_dir2/0013_hadoop_job_run.rb +29 -0
  108. data/spec/tengine/job/dsls/2003_expansion/expansion_5.rb +11 -0
  109. data/spec/tengine/job/dsls/VERSION +1 -0
  110. data/spec/tengine/job/dynamic_env_spec.rb +95 -0
  111. data/spec/tengine/job/edge_spec.rb +241 -0
  112. data/spec/tengine/job/element_selector_notation_spec.rb +354 -0
  113. data/spec/tengine/job/examples_spec.rb +62 -0
  114. data/spec/tengine/job/execution_spec.rb +100 -0
  115. data/spec/tengine/job/expansion_spec.rb +116 -0
  116. data/spec/tengine/job/hadoop_job_run_spec.rb +65 -0
  117. data/spec/tengine/job/job_spec.rb +4 -0
  118. data/spec/tengine/job/jobnet/1015_complecated_jobnet_spec.rb +72 -0
  119. data/spec/tengine/job/jobnet_actual_spec.rb +175 -0
  120. data/spec/tengine/job/jobnet_spec.rb +399 -0
  121. data/spec/tengine/job/jobnet_template_spec.rb +240 -0
  122. data/spec/tengine/job/killing_spec.rb +91 -0
  123. data/spec/tengine/job/reset_spec.rb +958 -0
  124. data/spec/tengine/job/reset_spec/4056_1_dump.txt +1 -0
  125. data/spec/tengine/job/root_jobnet_actual_spec.rb +89 -0
  126. data/spec/tengine/job/root_jobnet_template_spec.rb +248 -0
  127. data/spec/tengine/job/script_executable_spec.rb +132 -0
  128. data/spec/tengine/job/stoppable_spec.rb +176 -0
  129. data/spec/tengine/job/vertex_spec.rb +25 -0
  130. data/spec/tengine_job_spec.rb +4 -0
  131. data/tengine_job.gemspec +197 -0
  132. data/tmp/log/.gitignore +1 -0
  133. metadata +296 -0
@@ -0,0 +1,30 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ # スケジュール管理ドライバ
4
+ driver :schedule_driver do
5
+
6
+ on :'start.execution.job.tengine' do
7
+ exec = Tengine::Job::Signal.new(event).execution
8
+ name = exec.name_as_resource
9
+ status = Tengine::Core::Schedule::SCHEDULED
10
+ if exec.actual_base_timeout_alert && !exec.actual_base_timeout_alert.zero?
11
+ t1 = Time.now + (exec.actual_base_timeout_alert * 60.0)
12
+ Tengine::Core::Schedule.create event_type_name: "alert.execution.job.tengine", scheduled_at: t1, source_name: name, status: status , properties: event.properties
13
+ end
14
+ if exec.actual_base_timeout_termination && !exec.actual_base_timeout_termination.zero?
15
+ t2 = Time.now + (exec.actual_base_timeout_termination * 60.0)
16
+ Tengine::Core::Schedule.create event_type_name: "stop.execution.job.tengine", scheduled_at: t2, source_name: name, status: status, properties: event.properties.merge(stop_reason: 'timeout')
17
+ end
18
+ end
19
+
20
+ on :'success.execution.job.tengine' do
21
+ name = Tengine::Job::Signal.new(event).execution.name_as_resource
22
+ Tengine::Core::Schedule.where(source_name: name, status: Tengine::Core::Schedule::SCHEDULED).update_all(status: Tengine::Core::Schedule::INVALID)
23
+ end
24
+
25
+ on :'error.execution.job.tengine' do
26
+ name = Tengine::Job::Signal.new(event).execution.name_as_resource
27
+ Tengine::Core::Schedule.where(source_name: name, status: Tengine::Core::Schedule::SCHEDULED).update_all(status: Tengine::Core::Schedule::INVALID)
28
+ end
29
+
30
+ end
@@ -0,0 +1,12 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'tengine/job'
3
+
4
+ # ジョブDSLをロードする際に使用される語彙に関するメソッドを定義するモジュール
5
+ module Tengine::Job::DslBinder
6
+ include Tengine::Job::DslEvaluator
7
+
8
+ def jobnet(name, *args, &block)
9
+ Tengine::Job::RootJobnetTemplate.by_name(name)
10
+ end
11
+
12
+ end
@@ -0,0 +1,18 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'tengine/job'
3
+
4
+ # Tengine::Job::DslLoader と Tengine::Job::DslBinder がincludeしているモジュールです。
5
+ # それぞれに共通のメソッドを定義します。
6
+ module Tengine::Job::DslEvaluator
7
+ private
8
+ def __stack_instance_variable__(ivar_name, value)
9
+ backup = instance_variable_get(ivar_name)
10
+ instance_variable_set(ivar_name, value)
11
+ begin
12
+ return yield if block_given?
13
+ ensure
14
+ instance_variable_set(ivar_name, backup)
15
+ end
16
+ end
17
+
18
+ end
@@ -0,0 +1,180 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'tengine/job'
3
+
4
+ # ジョブDSLをロードする際に使用される語彙に関するメソッドを定義するモジュール
5
+ module Tengine::Job::DslLoader
6
+ include Tengine::Job::DslEvaluator
7
+
8
+ class << self
9
+ def loading_template_block_store
10
+ @loading_template_block_store ||= {}
11
+ end
12
+
13
+ def template_block_store
14
+ @template_block_store ||= {}
15
+ end
16
+
17
+ def template_block_store_key(job, name)
18
+ "#{job.root.id.to_s}/#{job.id.to_s}##{name}"
19
+ end
20
+
21
+ def update_loaded_blocks(loaded_root)
22
+ if loaded_root
23
+ loading_template_block_store.each do |unsaved_job, (name, block)|
24
+ loaded_job = loaded_root.vertex_by_name_path(unsaved_job.name_path)
25
+ key = template_block_store_key(loaded_job, name)
26
+ template_block_store[key] = block
27
+ end
28
+ else
29
+ loading_template_block_store.each do |saved_job, (name, block)|
30
+ key = template_block_store_key(saved_job, name)
31
+ template_block_store[key] = block
32
+ end
33
+ end
34
+ loading_template_block_store.clear
35
+ end
36
+ end
37
+
38
+
39
+ def jobnet(name, *args, &block)
40
+ options = args.extract_options!
41
+ options = {
42
+ :name => name,
43
+ :description => args.first || name,
44
+ }.update(options)
45
+ auto_sequence = options.delete(:auto_sequence)
46
+ result = __with_redirection__(options) do
47
+ if @jobnet.nil?
48
+ klass = Tengine::Job::RootJobnetTemplate
49
+ options[:dsl_version] = config.dsl_version
50
+ path, lineno = *block.source_location
51
+ options[:dsl_filepath] = config.relative_path_from_dsl_dir(path)
52
+ options[:dsl_lineno] = lineno.to_i
53
+ else
54
+ klass = Tengine::Job::JobnetTemplate
55
+ end
56
+ klass.new(options)
57
+ end
58
+ result.with_start
59
+ @jobnet.children << result if @jobnet
60
+ if result.parent.nil?
61
+ if duplicated = result.find_duplication
62
+ if (duplicated.dsl_filepath != result.dsl_filepath) ||
63
+ (duplicated.dsl_lineno != result.dsl_lineno)
64
+ raise Tengine::Job::DslError, "2 jobnet named #{name.inspect} found at #{duplicated.dsl_filepath}:#{duplicated.dsl_lineno} and #{result.dsl_filepath}:#{result.dsl_lineno}"
65
+ end
66
+ end
67
+ end
68
+
69
+ __stack_instance_variable__(:@auto_sequence, auto_sequence || @auto_sequence) do
70
+ __stack_instance_variable__(:@boot_job_names, []) do
71
+ __stack_instance_variable__(:@redirections, []) do
72
+ __stack_instance_variable__(:@jobnet, result, &block)
73
+ result.build_edges(@auto_sequence, @boot_job_names, @redirections)
74
+ end
75
+ end
76
+ end
77
+ if result.parent.nil?
78
+ loaded = result.find_duplication
79
+ result.save! unless loaded
80
+ Tengine::Job::DslLoader.update_loaded_blocks(loaded)
81
+ loaded || result
82
+ else
83
+ result
84
+ end
85
+ end
86
+
87
+ def auto_sequence
88
+ @auto_sequence = true
89
+ end
90
+
91
+ def boot_jobs(*boot_job_names)
92
+ @auto_sequence = false
93
+ @boot_job_names = boot_job_names
94
+ end
95
+
96
+ def job(name, *args)
97
+ script, description, options = __parse_job_args__(name, args)
98
+ options = {
99
+ :name => name,
100
+ :description => description,
101
+ :script => script
102
+ }.update(options)
103
+ preparation = options.delete(:preparation)
104
+ result = __with_redirection__(options) do
105
+ Tengine::Job::JobnetTemplate.new(options)
106
+ end
107
+ @jobnet.children << result
108
+ if preparation
109
+ Tengine::Job::DslLoader.loading_template_block_store[result] = [:preparation, preparation]
110
+ end
111
+ result
112
+ end
113
+
114
+ def hadoop_job_run(name, *args, &block)
115
+ script, description, options = __parse_job_args__(name, args)
116
+ options[:script] = script
117
+ options[:jobnet_type_key] = :hadoop_job_run
118
+ jobnet(name, description, options, &block)
119
+ end
120
+
121
+ def hadoop_job(name, options = {})
122
+ result = __with_redirection__(options) do
123
+ Tengine::Job::JobnetTemplate.new(:name => name, :jobnet_type_key => :hadoop_job)
124
+ end
125
+ result.children << start = Tengine::Job::Start.new
126
+ result.children << fork = Tengine::Job::Fork.new
127
+ result.children << map = Tengine::Job::JobnetTemplate.new(:name => "Map" , :jobnet_type_key => :map_phase )
128
+ result.children << reduce = Tengine::Job::JobnetTemplate.new(:name => "Reduce", :jobnet_type_key => :reduce_phase)
129
+ result.children << join = Tengine::Job::Join.new
130
+ result.children << _end = Tengine::Job::End.new
131
+ result.edges.new(:origin_id => start.id , :destination_id => fork.id )
132
+ result.edges.new(:origin_id => fork.id , :destination_id => map.id )
133
+ result.edges.new(:origin_id => fork.id , :destination_id => reduce.id)
134
+ result.edges.new(:origin_id => map.id , :destination_id => join.id )
135
+ result.edges.new(:origin_id => reduce.id, :destination_id => join.id )
136
+ result.edges.new(:origin_id => join.id , :destination_id => _end.id )
137
+ @jobnet.children << result
138
+ result
139
+ end
140
+
141
+ def finally(&block)
142
+ jobnet("finally", :jobnet_type_key => :finally, &block)
143
+ end
144
+
145
+ def expansion(root_jobnet_name, options = {})
146
+ options = {
147
+ :name => root_jobnet_name,
148
+ }.update(options)
149
+ result = __with_redirection__(options) do
150
+ Tengine::Job::Expansion.new(options)
151
+ end
152
+ @jobnet.children << result
153
+ result
154
+ end
155
+
156
+ private
157
+ def __parse_job_args__(name, args)
158
+ options = args.extract_options!
159
+ description_or_script, script = *args
160
+ if script
161
+ description = description_or_script
162
+ else
163
+ script = description_or_script
164
+ description = name
165
+ end
166
+ return script, description, options
167
+ end
168
+
169
+ def __with_redirection__(options)
170
+ destination_names = Array(options.delete(:to) || options.delete(:redirect_to))
171
+ result = yield
172
+ destination_names.each do |dest_name|
173
+ @redirections << [result.name, dest_name]
174
+ end
175
+ result
176
+ end
177
+
178
+
179
+
180
+ end
@@ -0,0 +1,150 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'tengine/job'
3
+ require 'selectable_attr'
4
+
5
+ # Vertexとともにジョブネットを構成するグラフの「辺」を表すモデル
6
+ # Tengine::Job::Jobnetにembeddedされます。
7
+ class Tengine::Job::Edge
8
+ include Mongoid::Document
9
+ include Mongoid::Timestamps
10
+ include Tengine::Core::SelectableAttr
11
+ include Tengine::Job::Signal::Transmittable
12
+
13
+ class StatusError < StandardError
14
+ end
15
+
16
+ embedded_in :owner, :class_name => "Tengine::Job::Jobnet", :inverse_of => :edges
17
+
18
+ field :phase_cd , :type => Integer, :default => 0 # ステータス。とりうる値は後述を参照してください。詳しくはtengine_jobパッケージ設計書の「edge状態遷移」を参照してください。
19
+ field :origin_id , :type => BSON::ObjectId # 辺の遷移元となるvertexのid
20
+ field :destination_id, :type => BSON::ObjectId # 辺の遷移先となるvertexのid
21
+
22
+ validates :origin_id, :presence => true
23
+ validates :destination_id, :presence => true
24
+
25
+ selectable_attr :phase_cd do
26
+ entry 0, :active , "active" , :alive => true
27
+ entry 10, :transmitting, "transmitting", :alive => true
28
+ entry 20, :transmitted , "transmitted" , :alive => false
29
+ entry 30, :suspended , "suspended" , :alive => true
30
+ entry 31, :keeping , "keeping" , :alive => true
31
+ entry 40, :closing , "closing" , :alive => false
32
+ entry 50, :closed , "closed" , :alive => false
33
+ end
34
+
35
+ def alive?; !!phase_entry[:alive]; end
36
+ def alive_or_closing?; alive? || closing?; end
37
+ def alive_or_closing_or_closed?; alive? || closing? || closed?; end
38
+
39
+ phase_keys.each do |phase_key|
40
+ class_eval(<<-END_OF_METHOD)
41
+ def #{phase_key}?; phase_key == #{phase_key.inspect}; end
42
+ END_OF_METHOD
43
+ end
44
+
45
+ def origin
46
+ owner.children.detect{|c| c.id == origin_id}
47
+ end
48
+
49
+ def destination
50
+ owner.children.detect{|c| c.id == destination_id}
51
+ end
52
+
53
+ def name_for_message
54
+ "edge(#{id.to_s}) from #{origin ? origin.name_path : 'no origin'} to #{destination ? destination.name_path : 'no destination'}"
55
+ end
56
+
57
+ def inspect
58
+ "#<#{self.class.name} #{name_for_message}>"
59
+ end
60
+
61
+ # https://cacoo.com/diagrams/hdLgrzYsTBBpV3Wj#3E9EA
62
+ def transmit(signal)
63
+ case phase_key
64
+ when :active then
65
+ d = destination
66
+ if signal.execution.in_scope?(d)
67
+ self.phase_key = :transmitting
68
+ signal.leave(self)
69
+ else
70
+ Tengine.logger.info("#{d.name_path} will not be executed, becauase it is out of execution scope.")
71
+ end
72
+ when :suspended then
73
+ self.phase_key = :keeping
74
+ when :closing then
75
+ self.phase_key = :closed
76
+ signal.paths << self
77
+ signal.with_paths_backup do
78
+ if destination.is_a?(Tengine::Job::Job)
79
+ destination.next_edges.first.transmit(signal)
80
+ else
81
+ signal.leave(self)
82
+ end
83
+ end
84
+ end
85
+ end
86
+
87
+ def complete(signal)
88
+ case phase_key
89
+ when :transmitting then
90
+ self.phase_key = :transmitted
91
+ when :active, :closed then
92
+ # IG
93
+ when :suspended, :keeping then
94
+ # N/A
95
+ raise Tengine::Job::Edge::StatusError, "#{self.class.name}#complete not available on #{phase_key.inspect} at #{self.inspect}"
96
+ end
97
+ end
98
+
99
+ def reset(signal)
100
+ # 全てのステータスから遷移する
101
+ if d = destination
102
+ if signal.execution.in_scope?(d)
103
+ self.phase_key = :active
104
+ d.reset(signal)
105
+ end
106
+ else
107
+ raise "destination not found: #{destination_id.inspect} from #{origin.inspect}"
108
+ end
109
+ end
110
+
111
+ def close(signal)
112
+ case phase_key
113
+ when :active, :suspended, :keeping, :transmitting then
114
+ self.phase_key = :closing
115
+ end
116
+ end
117
+
118
+ def close_followings
119
+ accept_visitor(Tengine::Job::Edge::Closer.new)
120
+ end
121
+
122
+ def accept_visitor(visitor)
123
+ visitor.visit(self)
124
+ end
125
+
126
+ def phase_key=(phase_key)
127
+ Tengine.logger.debug("edge phase changed. <#{self.id.to_s}> #{self.phase_name} -> #{Tengine::Job::Edge.phase_name_by_key(phase_key)}")
128
+ self.write_attribute(:phase_cd, Tengine::Job::Edge.phase_id_by_key(phase_key))
129
+ end
130
+
131
+ class Closer
132
+ def visit(obj)
133
+ if obj.is_a?(Tengine::Job::End)
134
+ if parent = obj.parent
135
+ (parent.next_edges || []).each{|edge| edge.accept_visitor(self)}
136
+ end
137
+ elsif obj.is_a?(Tengine::Job::Vertex)
138
+ obj.next_edges.each{|edge| edge.accept_visitor(self)}
139
+ elsif obj.is_a?(Tengine::Job::Edge)
140
+ obj.close(nil)
141
+ obj.destination.accept_visitor(self)
142
+ else
143
+ raise "Unsupported class #{obj.inspect}"
144
+ end
145
+ end
146
+
147
+ end
148
+
149
+
150
+ end
@@ -0,0 +1,169 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'tengine/job'
3
+
4
+ # # Tengine Jobnet Element Selector Notation
5
+ # Tengineジョブネット要素セレクタ記法
6
+ #
7
+ # Tengineジョブネット要素セレクタ記法は、TenigneのジョブDSLで生成されるジョブネットから
8
+ # 特定のVertexやEdgeを特定するための記述方法です。
9
+ # 正式名称が長いので、ここでは略して「セレクタ記法」と呼ぶ
10
+ #
11
+ # ## 背景
12
+ # Tengienジョブでは、ジョブやジョブネットなどのvertexとそれらを結ぶedgeが連携して動くこと
13
+ # ジョブの実行を行うが、ジョブ、ジョブネット以外のvertexとedgeには名前がついていない。
14
+ # しかし、ジョブネット内の要素群が連携して動作することを確認するためには、どのvertex、
15
+ # あるいはどのedgeなのかという点について状態などを確認する必要がある。
16
+ #
17
+ # ## name_path
18
+ # Tengineのジョブ/ジョブネットは親子関係によるツリー構造を持ち、
19
+ # それぞれ兄弟間では重複しない名前が指定されるので、スラッシュによって区切ることによって
20
+ # ファイルパスのような表記によって、どのジョブ/ジョブネットなのかを特定できる
21
+ #
22
+ # ## name_pathの問題点
23
+ # ジョブとジョブネットについては名前があるのでname_pathだけで特定できるが、
24
+ # それ以外のedgeやfork, joinについては特定することができない。
25
+ #
26
+ # ## それぞれの指定方法
27
+ # ### ジョブ/ジョブネット
28
+ # 1. name_pathをそのまま
29
+ # * 例: "/j100/j110/j111" # jxxx は、ジョブあるいはジョブネットを想定しています
30
+ # 2. "#{ジョブ/ジョブネット名}@#{ジョブネットのname_path}"
31
+ # * 例: "j111@/j100/j110"
32
+ #
33
+ #
34
+ # ### startとend
35
+ # 1. "#{start or end}!#{ジョブネットのセレクタ}"
36
+ # * 例: start!j110@/j100
37
+ # * 例: end!/j100/j110
38
+ # 2. "#{start or end}!#{ジョブネットのname_path}"
39
+ # * 例: start@/j100/j110
40
+ # * 例: end@/j100/j110
41
+ #
42
+ # ### edge
43
+ # #### ジョブ/ジョブネットの前後のedge
44
+ # 1. "#{prev or next}!#{ジョブ/ジョブネットのセレクタ}"
45
+ # * 例: prev!j110@/j100
46
+ # * 例: next!/j100/j110
47
+ #
48
+ # #### ジョブ/ジョブネットの間のedge
49
+ # 1. "#{前のジョブ名}~#{後のジョブ名}@#{ジョブネットのセレクタ}"
50
+ # * 例: j111~j112@/j100/j110
51
+ #
52
+ # #### fork-join間のedge
53
+ # 1. "fork~join!#{前のジョブ名}~#{後のジョブ名}@#{ジョブネットのセレクタ}"
54
+ # * 例: fork~join!j112~j113@/j100/j110
55
+ #
56
+ # ### fork, join
57
+ # 1. "#{fork or join}!#{前のジョブ名}~#{後のジョブ名}@#{ジョブネットのセレクタ}"
58
+ # * 例: fork!j112~j113@/j100/j110
59
+ # * 例: join!j112~j113@/j100/j110
60
+ #
61
+ #
62
+
63
+ module Tengine::Job::ElementSelectorNotation
64
+
65
+ class NotFound < Tengine::Errors::NotFound
66
+ attr_reader :jobnet, :notation
67
+ def initialize(jobnet, notation)
68
+ @jobnet, @notation = jobnet, notation
69
+ end
70
+
71
+ def message
72
+ "Tengine Jobnet Element not found by selector #{notation.inspect} in #{@jobnet.name_path}"
73
+ end
74
+ end
75
+
76
+
77
+ NAME_PART = /[A-Za-z_][\w\-]*/.freeze
78
+ NAME_PATH_PART = /[A-Za-z_\/][\w\-\/]*/.freeze
79
+
80
+ # elementメソッドは指定されたnotationによって対象となる要素が見つからなかった場合はnilを返します。
81
+ # 例外をraiseさせたい場合は element!メソッドを使ってください。
82
+ def element(notation)
83
+ direction, current_path = *notation.split(/@/, 2)
84
+ return vertex_by_name_path(direction) if current_path.nil? && Tengine::Job::NamePath.absolute?(direction)
85
+ current = current_path ? vertex_by_name_path(current_path) : self
86
+ raise "#{current_path.inspect} not found" unless current
87
+ case direction
88
+ # when /^prev!(?:#{Tengine::Core::Validation::BASE_NAME.format})/
89
+ when /^(prev|next)!(#{NAME_PATH_PART})$/ then
90
+ job = $2 ? current.vertex_by_name_path($2) : self
91
+ job.send("#{$1}_edges").first
92
+ when /^(start|end|finally)!(#{NAME_PATH_PART})$/ then
93
+ job = $2 ? current.vertex_by_name_path($2) : self
94
+ job.child_by_name($1)
95
+ when /^(start|end|finally)$/ then
96
+ current.child_by_name($1)
97
+ when /^(#{NAME_PART})~(#{NAME_PART})$/ then
98
+ job1 = current.child_by_name($1)
99
+ job2 = current.child_by_name($2)
100
+ job1.next_edges.detect{|edge| edge.destination_id == job2.id}
101
+ when /^(fork|join)!(#{NAME_PART})~(#{NAME_PART})$/ then
102
+ klass = Tengine::Job.const_get($1.capitalize)
103
+ job1 = current.child_by_name($2)
104
+ job2 = current.child_by_name($3)
105
+ paths = PathFinder.new(job1, job2).process
106
+ paths.each do |path|
107
+ path.each do |element|
108
+ return element if element.is_a?(klass)
109
+ end
110
+ end
111
+ when /^fork~join!(#{NAME_PART})~(#{NAME_PART})$/ then
112
+ job1 = current.child_by_name($1)
113
+ job2 = current.child_by_name($2)
114
+ paths = PathFinder.new(job1, job2).process
115
+ paths.each do |path|
116
+ path.each do |element|
117
+ if element.is_a?(Tengine::Job::Edge)
118
+ if element.origin.is_a?(Tengine::Job::Fork) &&
119
+ element.destination.is_a?(Tengine::Job::Join)
120
+ return element
121
+ end
122
+ end
123
+ end
124
+ end
125
+ else
126
+ current.child_by_name(direction)
127
+ end
128
+ end
129
+
130
+ # element!メソッドは指定されたnotationによって対象となる要素が見つからなかった場合はnilを返します。
131
+ # 例外をraiseさせたい場合は element!メソッドを使ってください。
132
+ def element!(notation, *args)
133
+ result = element(notation, *args)
134
+ raise NotFound.new(self, notation) unless result
135
+ result
136
+ end
137
+
138
+ class PathFinder
139
+ def initialize(origin, dest)
140
+ @origin, @dest = origin, dest
141
+ end
142
+
143
+ def process
144
+ @routes = []
145
+ @current_route = []
146
+ @origin.accept_visitor(self)
147
+ @routes.select{|route| route.last == @dest}
148
+ end
149
+
150
+ def visit(element)
151
+ @current_route << element
152
+ if element.is_a?(Tengine::Job::Edge)
153
+ element.destination.accept_visitor(self)
154
+ else
155
+ @routes << @current_route.dup
156
+ return if element == @dest
157
+ element.next_edges.each do |edge|
158
+ bak = @current_route.dup
159
+ begin
160
+ edge.accept_visitor(self)
161
+ ensure
162
+ @current_route = bak
163
+ end
164
+ end
165
+ end
166
+ end
167
+ end
168
+
169
+ end