tengine_job 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +15 -0
  2. data/Gemfile.lock +78 -48
  3. data/bin/tengine_job +71 -0
  4. data/examples/0004_retry_one_layer.rb +10 -7
  5. data/examples/0027_parallel_ssh_job +9 -0
  6. data/examples/0027_parallel_ssh_jobs.rb +14 -0
  7. data/lib/tengine/job.rb +19 -49
  8. data/lib/tengine/job/dsl.rb +13 -0
  9. data/lib/tengine/job/{dsl_binder.rb → dsl/binder.rb} +4 -4
  10. data/lib/tengine/job/{dsl_evaluator.rb → dsl/evaluator.rb} +2 -2
  11. data/lib/tengine/job/{dsl_loader.rb → dsl/loader.rb} +20 -22
  12. data/lib/tengine/job/runtime.rb +32 -0
  13. data/lib/tengine/job/{drivers → runtime/drivers}/job_control_driver.rb +46 -92
  14. data/lib/tengine/job/{drivers → runtime/drivers}/job_execution_driver.rb +14 -10
  15. data/lib/tengine/job/runtime/drivers/jobnet_control_driver.rb +240 -0
  16. data/lib/tengine/job/{drivers → runtime/drivers}/schedule_driver.rb +4 -4
  17. data/lib/tengine/job/{edge.rb → runtime/edge.rb} +79 -25
  18. data/lib/tengine/job/{executable.rb → runtime/executable.rb} +35 -15
  19. data/lib/tengine/job/{execution.rb → runtime/execution.rb} +19 -11
  20. data/lib/tengine/job/runtime/job_base.rb +5 -0
  21. data/lib/tengine/job/runtime/jobnet.rb +283 -0
  22. data/lib/tengine/job/runtime/junction.rb +44 -0
  23. data/lib/tengine/job/runtime/named_vertex.rb +95 -0
  24. data/lib/tengine/job/runtime/root_jobnet.rb +81 -0
  25. data/lib/tengine/job/{signal.rb → runtime/signal.rb} +99 -13
  26. data/lib/tengine/job/runtime/ssh_job.rb +486 -0
  27. data/lib/tengine/job/{jobnet → runtime}/state_transition.rb +6 -4
  28. data/lib/tengine/job/runtime/stoppable.rb +64 -0
  29. data/lib/tengine/job/runtime/vertex.rb +50 -0
  30. data/lib/tengine/job/structure.rb +20 -0
  31. data/lib/tengine/job/{category.rb → structure/category.rb} +9 -5
  32. data/lib/tengine/job/{jobnet/builder.rb → structure/edge_builder.rb} +11 -7
  33. data/lib/tengine/job/{element_selector_notation.rb → structure/element_selector_notation.rb} +15 -11
  34. data/lib/tengine/job/structure/jobnet_builder.rb +83 -0
  35. data/lib/tengine/job/structure/jobnet_finder.rb +60 -0
  36. data/lib/tengine/job/{name_path.rb → structure/name_path.rb} +2 -2
  37. data/lib/tengine/job/structure/tree.rb +20 -0
  38. data/lib/tengine/job/structure/visitor.rb +67 -0
  39. data/lib/tengine/job/template.rb +24 -0
  40. data/lib/tengine/job/template/edge.rb +37 -0
  41. data/lib/tengine/job/template/expansion.rb +24 -0
  42. data/lib/tengine/job/template/generator.rb +111 -0
  43. data/lib/tengine/job/template/jobnet.rb +83 -0
  44. data/lib/tengine/job/template/junction.rb +14 -0
  45. data/lib/tengine/job/{job.rb → template/named_vertex.rb} +3 -5
  46. data/lib/tengine/job/{root_jobnet_template.rb → template/root_jobnet.rb} +12 -26
  47. data/lib/tengine/job/template/ssh_job.rb +80 -0
  48. data/lib/tengine/job/template/vertex.rb +97 -0
  49. metadata +127 -93
  50. data/lib/tengine/job/connectable.rb +0 -43
  51. data/lib/tengine/job/drivers/jobnet_control_driver.rb +0 -249
  52. data/lib/tengine/job/end.rb +0 -32
  53. data/lib/tengine/job/expansion.rb +0 -37
  54. data/lib/tengine/job/fork.rb +0 -6
  55. data/lib/tengine/job/jobnet.rb +0 -184
  56. data/lib/tengine/job/jobnet/job_state_transition.rb +0 -167
  57. data/lib/tengine/job/jobnet/jobnet_state_transition.rb +0 -110
  58. data/lib/tengine/job/jobnet_actual.rb +0 -84
  59. data/lib/tengine/job/jobnet_template.rb +0 -10
  60. data/lib/tengine/job/join.rb +0 -6
  61. data/lib/tengine/job/junction.rb +0 -29
  62. data/lib/tengine/job/killing.rb +0 -30
  63. data/lib/tengine/job/mm_compatibility.rb +0 -6
  64. data/lib/tengine/job/mm_compatibility/connectable.rb +0 -13
  65. data/lib/tengine/job/root.rb +0 -16
  66. data/lib/tengine/job/root_jobnet_actual.rb +0 -58
  67. data/lib/tengine/job/script_executable.rb +0 -235
  68. data/lib/tengine/job/start.rb +0 -20
  69. data/lib/tengine/job/stoppable.rb +0 -15
  70. data/lib/tengine/job/vertex.rb +0 -181
@@ -1,7 +1,7 @@
1
1
  # -*- coding: utf-8 -*-
2
- require 'tengine/job/jobnet'
2
+ require 'tengine/job/runtime'
3
3
 
4
- module Tengine::Job::Jobnet::StateTransition
4
+ module Tengine::Job::Runtime::StateTransition
5
5
 
6
6
  def self.included(mod)
7
7
  mod.extend(ClassMethods)
@@ -23,10 +23,12 @@ module Tengine::Job::Jobnet::StateTransition
23
23
  def #{method_name}(*args, &block)
24
24
  case self.phase_key
25
25
  when #{available_phase_keys.map(&:inspect).join(', ')} then
26
- #{original_method}(*args, &block)
26
+ update_with_lock do
27
+ #{original_method}(*args, &block)
28
+ end
27
29
  #{ignore_case}
28
30
  else
29
- raise Tengine::Job::Executable::PhaseError, "\#{name_path} \#{self.class.name}##{method_name} not available when the phase_key of \#{self.name_path.inspect} is \#{self.phase_key.inspect}"
31
+ raise Tengine::Job::Runtime::Executable::PhaseError, "\#{name_path} \#{self.class.name}##{method_name} not available when the phase_key of \#{self.name_path.inspect} is \#{self.phase_key.inspect}"
30
32
  end
31
33
  end
32
34
  EOS
@@ -0,0 +1,64 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'tengine/job/runtime'
3
+ require 'selectable_attr'
4
+
5
+ # ジョブ/ジョブネットを実行する際の情報に関するモジュール
6
+ # Tengine::Job::Runtime::Jobnet, Tengine::Job::Template::Jobnetがこのモジュールをincludeします
7
+ module Tengine::Job::Runtime::Stoppable
8
+ extend ActiveSupport::Concern
9
+
10
+ included do
11
+ field :stopped_at , :type => DateTime # 停止時刻。停止を開始した時刻です。
12
+ field :stop_reason, :type => String # 停止理由。手動以外での停止ならば停止した理由が設定されます。
13
+ end
14
+
15
+ # https://www.pivotaltracker.com/story/show/23329935
16
+
17
+ def stop_reason= r
18
+ super
19
+ children.each do |i|
20
+ if i.respond_to?(:chained_box?) && i.chained_box?
21
+ i.stop_reason = r
22
+ end
23
+ end
24
+ end
25
+
26
+ def stopped_at= t
27
+ super
28
+ children.each do |i|
29
+ if i.respond_to?(:chained_box?) && i.chained_box?
30
+ i.stopped_at = t
31
+ end
32
+ end
33
+ end
34
+
35
+ def fire_stop_event(root_jobnet, options = Hash.new)
36
+ root_jobnet_id = root_jobnet.id.to_s
37
+ result = Tengine::Job::Runtime::Execution.create!(
38
+ options.merge(:root_jobnet_id => root_jobnet_id))
39
+ properties = {
40
+ :execution_id => result.id.to_s,
41
+ :root_jobnet_id => root_jobnet_id,
42
+ :stop_reason => "user_stop"
43
+ }
44
+
45
+ target_id = self.id.to_s
46
+ # if target.children.blank?
47
+ if script_executable?
48
+ event = :"stop.job.job.tengine"
49
+ properties[:target_job_id] = target_id
50
+ properties[:target_jobnet_id] = parent.id.to_s
51
+ else
52
+ event = :"stop.jobnet.job.tengine"
53
+ properties[:target_jobnet_id] = target_id
54
+ end
55
+
56
+ EM.run do
57
+ Tengine::Event.fire(event,
58
+ :source_name => name_as_resource,
59
+ :properties => properties)
60
+ end
61
+
62
+ return result
63
+ end
64
+ end
@@ -0,0 +1,50 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'tengine/job/runtime'
3
+
4
+ # Edgeとともにジョブネットを構成するグラフの「頂点」を表すモデルです。
5
+ # このクラスだけでツリー構造を作ることができますが、ほぼ抽象クラスであり実際には
6
+ # 派生クラスのオブジェクトによってツリー構造が作られます。
7
+ class Tengine::Job::Runtime::Vertex
8
+ include Mongoid::Document
9
+ include Mongoid::Timestamps
10
+ include Tengine::Job::Structure::NamePath
11
+ include Tengine::Job::Structure::Tree
12
+ include Tengine::Job::Structure::Visitor::Accepter
13
+ include Tengine::Job::Runtime::Signal::Transmittable
14
+
15
+ field :child_index, type: Integer
16
+
17
+ # self.cyclic = true
18
+ with_options(class_name: self.name, foreign_key: "parent_id") do |c|
19
+ c.belongs_to :parent , inverse_of: :children
20
+ c.has_many :children, inverse_of: :parent , validate: false, order: {child_index: 1}
21
+ end
22
+
23
+ def template?; false; end
24
+ def runtime?; !template?; end
25
+
26
+ def previous_edges
27
+ return nil unless parent
28
+ parent.edges.select{|edge| edge.destination_id == self.id}
29
+ end
30
+ alias_method :prev_edges, :previous_edges
31
+
32
+ def next_edges
33
+ return nil unless parent
34
+ parent.edges.select{|edge| edge.origin_id == self.id}
35
+ end
36
+
37
+ def ancestors_until_expansion
38
+ if parent = self.parent
39
+ parent.ancestors_until_expansion + [parent]
40
+ else
41
+ []
42
+ end
43
+ end
44
+
45
+ # Tengine::Job::Runtime::Vertexは構成されるツリーのルートを保存しても、embedでないので
46
+ # 各vertexをsaveしないと保存されないため、明示的に保存しています。
47
+ def save_descendants!
48
+ accept_visitor(Tengine::Job::Structure::Visitor::All.new{|v| v.save! })
49
+ end
50
+ end
@@ -0,0 +1,20 @@
1
+ require 'tengine/job'
2
+
3
+ module Tengine::Job::Structure
4
+ autoload :Category , "tengine/job/structure/category"
5
+ autoload :NamePath , "tengine/job/structure/name_path"
6
+ autoload :Tree , "tengine/job/structure/tree"
7
+
8
+ autoload :Visitor , "tengine/job/structure/visitor"
9
+
10
+ autoload :EdgeBuilder , "tengine/job/structure/edge_builder"
11
+
12
+ autoload :JobnetBuilder , "tengine/job/structure/jobnet_builder"
13
+ autoload :JobnetFinder , "tengine/job/structure/jobnet_finder"
14
+
15
+ autoload :ElementSelectorNotation, "tengine/job/structure/element_selector_notation"
16
+
17
+ class Error < StandardError
18
+ end
19
+
20
+ end
@@ -1,10 +1,10 @@
1
1
  # -*- coding: utf-8 -*-
2
- require 'tengine/job'
2
+ require 'tengine/job/template'
3
3
 
4
4
  require 'yaml'
5
5
  require 'tengine/support/yaml_with_erb'
6
6
 
7
- class Tengine::Job::Category
7
+ class Tengine::Job::Structure::Category
8
8
  include Mongoid::Document
9
9
  include Mongoid::Timestamps
10
10
  include Tengine::Core::FindByName
@@ -12,16 +12,20 @@ class Tengine::Job::Category
12
12
  field :name , :type => String # カテゴリ名。ディレクトリ名を元に設定されるので、"/"などは使用不可。
13
13
  field :caption , :type => String # カテゴリの表示名。各ディレクトリ名に対応する表示名。通常dictionary.ymlに定義する。
14
14
 
15
- with_options(:class_name => "Tengine::Job::Category") do |c|
15
+ with_options(:class_name => self.name) do |c|
16
16
  c.belongs_to :parent, :inverse_of => :children, :index => true
17
17
  c.has_many :children, :inverse_of => :parent, :order => [:name, :asc]
18
18
  end
19
19
 
20
+ # embeddedなDocumentをその外部のドキュメントから参照することはできないので、これはNG
21
+ # has_many :template_root_jobnets, class_name: "Tengine::Job::Template::RootJobnet", inverse_of: :category
22
+ has_many :runtime_root_jobnets , class_name: "Tengine::Job::Runtime::RootJobnet" , inverse_of: :category
23
+
20
24
  class << self
21
25
  def update_for(base_dir)
22
26
  root_dir = File.basename(base_dir)
23
27
  dic_dir_base = File.dirname(base_dir)
24
- root_jobnets = Tengine::Job::RootJobnetTemplate.all
28
+ root_jobnets = Tengine::Job::Template::RootJobnet.all
25
29
  root_jobnets.each do |root_jobnet|
26
30
  dirs = File.dirname(root_jobnet.dsl_filepath || "").split('/') - ['.', '..']
27
31
  dirs.unshift(root_dir)
@@ -35,7 +39,7 @@ class Tengine::Job::Category
35
39
  hash = YAML.load_file(dic_path)
36
40
  caption = hash[dir]
37
41
  end
38
- category = Tengine::Job::Category.find_or_create_by(
42
+ category = self.find_or_create_by(
39
43
  :name => dir,
40
44
  :caption => caption || dir,
41
45
  :parent_id => last_category ? last_category.id : nil)
@@ -1,15 +1,19 @@
1
1
  # -*- coding: utf-8 -*-
2
+ require 'tengine/job/structure'
3
+
2
4
  require 'tsort'
3
5
 
4
- class Tengine::Job::Jobnet::Builder
6
+ class Tengine::Job::Structure::EdgeBuilder
5
7
  include TSort
6
8
 
7
- def initialize(client, boot_job_names, redirections)
9
+ def initialize(client, boot_job_names, redirections, options = {})
8
10
  @client, @boot_job_names, @redirections = client, boot_job_names, redirections.dup
9
11
  @graph = Hash.new do |h, k| h[k] = Array.new end
10
12
  @redirections.each do |(x, y)|
11
13
  @graph[x] << y
12
14
  end
15
+ @fork_class = options[:fork_class]
16
+ @join_class = options[:join_class]
13
17
  end
14
18
 
15
19
  def children; @client.children; end
@@ -20,7 +24,7 @@ class Tengine::Job::Jobnet::Builder
20
24
  def process
21
25
  tsort
22
26
  rescue TSort::Cyclic
23
- raise Tengine::Job::DslError, "circular dependency found in jobnet ``#{@client.name}''"
27
+ raise Tengine::Job::Structure::Error, "circular dependency found in jobnet ``#{@client.name}''"
24
28
  else
25
29
  build_start_edges
26
30
  build_edge_by_redirections
@@ -46,7 +50,7 @@ class Tengine::Job::Jobnet::Builder
46
50
  when 1 then
47
51
  new_edge(start, child_by_name(@boot_job_names.first))
48
52
  else
49
- fork = Tengine::Job::Fork.new
53
+ fork = @fork_class.new
50
54
  children << fork
51
55
  new_edge(start, fork)
52
56
  @boot_job_names.each do |boot_job_name|
@@ -87,7 +91,7 @@ class Tengine::Job::Jobnet::Builder
87
91
  def build_forks_and_edges
88
92
  @fork_origin_to_fork = {}
89
93
  @fork_origins.each do |fork_origin|
90
- children << fork = Tengine::Job::Fork.new
94
+ children << fork = @fork_class.new
91
95
  @fork_origin_to_fork[fork_origin] = fork
92
96
  new_edge(child_by_name(fork_origin), fork)
93
97
  @redirections.dup.
@@ -101,7 +105,7 @@ class Tengine::Job::Jobnet::Builder
101
105
  def build_joins_and_edges
102
106
  @join_destination_to_join = {}
103
107
  @join_destinations.each do |join_destination|
104
- children << join = Tengine::Job::Join.new
108
+ children << join = @join_class.new
105
109
  @join_destination_to_join[join_destination] = join
106
110
  @redirections.dup.
107
111
  delete_if{|r| @fork_to_join.include?(r)}.
@@ -132,7 +136,7 @@ class Tengine::Job::Jobnet::Builder
132
136
  when 0 then raise "Must be a bug!!!"
133
137
  when 1 then new_edge(child_by_name(end_points.first), _end)
134
138
  else
135
- join = Tengine::Job::Join.new
139
+ join = @join_class.new
136
140
  children << join
137
141
  end_points.each{|point| new_edge(child_by_name(point), join)}
138
142
  new_edge(join, _end)
@@ -1,5 +1,5 @@
1
1
  # -*- coding: utf-8 -*-
2
- require 'tengine/job'
2
+ require 'tengine/job/structure'
3
3
 
4
4
  # # Tengine Jobnet Element Selector Notation
5
5
  # Tengineジョブネット要素セレクタ記法
@@ -60,7 +60,7 @@ require 'tengine/job'
60
60
  #
61
61
  #
62
62
 
63
- module Tengine::Job::ElementSelectorNotation
63
+ module Tengine::Job::Structure::ElementSelectorNotation
64
64
 
65
65
  class NotFound < Tengine::Errors::NotFound
66
66
  attr_reader :jobnet, :notation
@@ -73,6 +73,9 @@ module Tengine::Job::ElementSelectorNotation
73
73
  end
74
74
  end
75
75
 
76
+ def base_module
77
+ self.template? ? Tengine::Job::Template : Tengine::Job::Runtime
78
+ end
76
79
 
77
80
  NAME_PART = /[A-Za-z_][\w\-]*/.freeze
78
81
  NAME_PATH_PART = /[A-Za-z_\/][\w\-\/]*/.freeze
@@ -81,7 +84,7 @@ module Tengine::Job::ElementSelectorNotation
81
84
  # 例外をraiseさせたい場合は element!メソッドを使ってください。
82
85
  def element(notation)
83
86
  direction, current_path = *notation.split(/@/, 2)
84
- return vertex_by_name_path(direction) if current_path.nil? && Tengine::Job::NamePath.absolute?(direction)
87
+ return vertex_by_name_path(direction) if current_path.nil? && Tengine::Job::Structure::NamePath.absolute?(direction)
85
88
  current = current_path ? vertex_by_name_path(current_path) : self
86
89
  raise "#{current_path.inspect} not found" unless current
87
90
  case direction
@@ -99,10 +102,10 @@ module Tengine::Job::ElementSelectorNotation
99
102
  job2 = current.child_by_name($2)
100
103
  job1.next_edges.detect{|edge| edge.destination_id == job2.id}
101
104
  when /^(fork|join)!(#{NAME_PART})~(#{NAME_PART})$/ then
102
- klass = Tengine::Job.const_get($1.capitalize)
105
+ klass = base_module.const_get($1.capitalize)
103
106
  job1 = current.child_by_name($2)
104
107
  job2 = current.child_by_name($3)
105
- paths = PathFinder.new(job1, job2).process
108
+ paths = PathFinder.new(self, job1, job2).process
106
109
  paths.each do |path|
107
110
  path.each do |element|
108
111
  return element if element.is_a?(klass)
@@ -111,12 +114,12 @@ module Tengine::Job::ElementSelectorNotation
111
114
  when /^fork~join!(#{NAME_PART})~(#{NAME_PART})$/ then
112
115
  job1 = current.child_by_name($1)
113
116
  job2 = current.child_by_name($2)
114
- paths = PathFinder.new(job1, job2).process
117
+ paths = PathFinder.new(self, job1, job2).process
115
118
  paths.each do |path|
116
119
  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
+ if element.is_a?(base_module.const_get(:Edge))
121
+ if element.origin.is_a?(base_module.const_get(:Fork)) &&
122
+ element.destination.is_a?(base_module.const_get(:Join))
120
123
  return element
121
124
  end
122
125
  end
@@ -136,7 +139,8 @@ module Tengine::Job::ElementSelectorNotation
136
139
  end
137
140
 
138
141
  class PathFinder
139
- def initialize(origin, dest)
142
+ def initialize(client, origin, dest)
143
+ @client = client
140
144
  @origin, @dest = origin, dest
141
145
  end
142
146
 
@@ -149,7 +153,7 @@ module Tengine::Job::ElementSelectorNotation
149
153
 
150
154
  def visit(element)
151
155
  @current_route << element
152
- if element.is_a?(Tengine::Job::Edge)
156
+ if element.is_a?(@client.base_module.const_get(:Edge))
153
157
  element.destination.accept_visitor(self)
154
158
  else
155
159
  @routes << @current_route.dup
@@ -0,0 +1,83 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'tengine/job/structure'
3
+
4
+ module Tengine::Job::Structure::JobnetBuilder
5
+
6
+ def start_vertex
7
+ self.children.detect{|child| child.is_a?(self.class.start_vertex_class)}
8
+ end
9
+
10
+ def end_vertex
11
+ self.children.detect{|child| child.is_a?(self.class.end_vertex_class)}
12
+ end
13
+
14
+ def finally_vertex
15
+ self.children.detect{|child| child.is_a?(self.class.jobnet_class) && (child.jobnet_type_key == :finally)}
16
+ end
17
+ alias_method :finally_jobnet, :finally_vertex
18
+
19
+ def with_start
20
+ self.children << self.class.start_vertex_class.new
21
+ self
22
+ end
23
+
24
+ def prepare_end
25
+ if self.children.last.is_a?(self.class.end_vertex_class)
26
+ _end = self.children.last
27
+ yield(_end) if block_given?
28
+ else
29
+ _end = self.class.end_vertex_class.new
30
+ yield(_end) if block_given?
31
+ self.children << _end
32
+ end
33
+ _end
34
+ end
35
+
36
+ def child_by_name(str)
37
+ case str
38
+ when '..' then parent
39
+ when '.' then self
40
+ when 'start' then start_vertex
41
+ when 'end' then end_vertex
42
+ when 'finally' then finally_vertex
43
+ else
44
+ self.children.detect{|c| c.respond_to?(:name) && (c.name == str)}
45
+ end
46
+ end
47
+
48
+ def build_edges(auto_sequence, boot_job_names, redirections)
49
+ if self.children.length == 1 # 最初に追加したStartだけなら。
50
+ self.children.delete_all
51
+ return
52
+ end
53
+ if auto_sequence || boot_job_names.empty?
54
+ prepare_end
55
+ build_sequencial_edges
56
+ else
57
+ edge_builder = Tengine::Job::Structure::EdgeBuilder.new(self, boot_job_names, redirections,
58
+ fork_class: self.class.fork_class,
59
+ join_class: self.class.join_class
60
+ )
61
+ edge_builder.process
62
+ end
63
+ end
64
+
65
+ def build_sequencial_edges
66
+ self.edges.clear
67
+ current = nil
68
+ self.children.each do |child|
69
+ next if child.is_a?(self.class.jobnet_class) && !!child.jobnet_type_entry[:alternative]
70
+ if current
71
+ edge = self.new_edge(current, child)
72
+ yield(edge) if block_given?
73
+ end
74
+ current = child
75
+ end
76
+ end
77
+
78
+ def new_edge(origin, destination)
79
+ origin_id = origin.is_a?(self.class.vertex_class) ? origin.id : origin
80
+ destination_id = destination.is_a?(self.class.vertex_class) ? destination.id : destination
81
+ edges.new(:origin_id => origin_id, :destination_id => destination_id)
82
+ end
83
+ end
@@ -0,0 +1,60 @@
1
+ require 'tengine/job/structure'
2
+
3
+ module Tengine::Job::Structure::JobnetFinder
4
+
5
+ def find_descendant_edge(edge_id)
6
+ edge_id = String(edge_id)
7
+ visitor = Tengine::Job::Structure::Visitor::Any.new do |vertex|
8
+ if vertex.respond_to?(:edges)
9
+ vertex.edges.detect{|edge| edge.id.to_s == edge_id}
10
+ else
11
+ nil
12
+ end
13
+ end
14
+ visitor.visit(self)
15
+ end
16
+ alias_method :edge, :find_descendant_edge
17
+
18
+ def find_descendant(vertex_id)
19
+ vertex_id = String(vertex_id)
20
+ return nil if vertex_id == self.id.to_s
21
+ vertex(vertex_id)
22
+ end
23
+
24
+ def vertex(vertex_id)
25
+ vertex_id = String(vertex_id)
26
+ return self if vertex_id == self.id.to_s
27
+ visitor = Tengine::Job::Structure::Visitor::Any.new{|v| vertex_id == v.id.to_s ? v : nil }
28
+ visitor.visit(self)
29
+ end
30
+
31
+ def find_descendant_by_name_path(name_path)
32
+ return nil if name_path == self.name_path
33
+ vertex_by_name_path(name_path)
34
+ end
35
+
36
+ def vertex_by_name_path(name_path)
37
+ Tengine::Job::Structure::NamePath.absolute?(name_path) ?
38
+ root.vertex_by_absolute_name_path(name_path) :
39
+ vertex_by_relative_name_path(name_path)
40
+ end
41
+
42
+ def vertex_by_absolute_name_path(name_path)
43
+ return self if name_path.to_s == self.name_path
44
+ visitor = Tengine::Job::Structure::Visitor::Any.new do |vertex|
45
+ if name_path == (vertex.respond_to?(:name_path) ? vertex.name_path : nil)
46
+ vertex
47
+ else
48
+ nil
49
+ end
50
+ end
51
+ visitor.visit(self)
52
+ end
53
+
54
+ def vertex_by_relative_name_path(name_path)
55
+ head, tail = *name_path.split(Tengine::Job::Structure::NamePath::SEPARATOR, 2)
56
+ child = child_by_name(head)
57
+ tail ? child.vertex_by_relative_name_path(tail) : child
58
+ end
59
+
60
+ end