droonga-engine 1.0.9 → 1.1.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 (195) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -0
  3. data/benchmark/timer-watcher/benchmark.rb +44 -0
  4. data/bin/droonga-engine-absorb-data +246 -187
  5. data/bin/droonga-engine-catalog-generate +12 -12
  6. data/bin/droonga-engine-catalog-modify +4 -4
  7. data/bin/droonga-engine-join +352 -171
  8. data/bin/droonga-engine-set-role +54 -0
  9. data/bin/droonga-engine-unjoin +107 -112
  10. data/droonga-engine.gemspec +3 -3
  11. data/install.sh +55 -36
  12. data/install/centos/functions.sh +2 -2
  13. data/install/debian/functions.sh +2 -2
  14. data/lib/droonga/address.rb +26 -24
  15. data/lib/droonga/buffered_tcp_socket.rb +65 -10
  16. data/lib/droonga/catalog/base.rb +9 -6
  17. data/lib/droonga/catalog/dataset.rb +17 -41
  18. data/lib/droonga/catalog/fetcher.rb +64 -0
  19. data/lib/droonga/catalog/generator.rb +245 -0
  20. data/lib/droonga/catalog/loader.rb +66 -0
  21. data/lib/droonga/{catalog_modifier.rb → catalog/modifier.rb} +11 -18
  22. data/lib/droonga/catalog/replicas_volume.rb +123 -0
  23. data/lib/droonga/catalog/schema.rb +37 -37
  24. data/lib/droonga/catalog/single_volume.rb +11 -3
  25. data/lib/droonga/catalog/slice.rb +10 -6
  26. data/lib/droonga/catalog/{collection_volume.rb → slices_volume.rb} +47 -11
  27. data/lib/droonga/catalog/version1.rb +47 -19
  28. data/lib/droonga/catalog/version2.rb +11 -10
  29. data/lib/droonga/catalog/version2_validator.rb +4 -4
  30. data/lib/droonga/catalog/volume.rb +17 -5
  31. data/lib/droonga/changable.rb +25 -0
  32. data/lib/droonga/cluster.rb +237 -0
  33. data/lib/droonga/collector_runner.rb +4 -0
  34. data/lib/droonga/collectors.rb +2 -1
  35. data/lib/droonga/collectors/recursive_sum.rb +26 -0
  36. data/lib/droonga/command/droonga_engine.rb +404 -127
  37. data/lib/droonga/command/droonga_engine_service.rb +47 -11
  38. data/lib/droonga/command/droonga_engine_worker.rb +21 -1
  39. data/lib/droonga/command/remote_command_base.rb +78 -0
  40. data/lib/droonga/command/serf_event_handler.rb +29 -20
  41. data/lib/droonga/data_absorber_client.rb +222 -0
  42. data/lib/droonga/database_scanner.rb +106 -0
  43. data/lib/droonga/{live_nodes_list_loader.rb → deferrable.rb} +11 -24
  44. data/lib/droonga/differ.rb +58 -0
  45. data/lib/droonga/dispatcher.rb +155 -32
  46. data/lib/droonga/distributed_command_planner.rb +9 -11
  47. data/lib/droonga/engine.rb +83 -78
  48. data/lib/droonga/engine/version.rb +1 -1
  49. data/lib/droonga/engine_node.rb +301 -0
  50. data/lib/droonga/engine_state.rb +62 -40
  51. data/lib/droonga/farm.rb +44 -5
  52. data/lib/droonga/file_observer.rb +16 -12
  53. data/lib/droonga/fluent_message_receiver.rb +98 -29
  54. data/lib/droonga/fluent_message_sender.rb +30 -23
  55. data/lib/droonga/forward_buffer.rb +160 -0
  56. data/lib/droonga/forwarder.rb +73 -40
  57. data/lib/droonga/handler.rb +7 -6
  58. data/lib/droonga/handler_messenger.rb +15 -6
  59. data/lib/droonga/handler_runner.rb +6 -1
  60. data/lib/droonga/internal_fluent_message_receiver.rb +28 -8
  61. data/lib/droonga/job_pusher.rb +10 -7
  62. data/lib/droonga/job_receiver.rb +6 -4
  63. data/lib/droonga/logger.rb +7 -1
  64. data/lib/droonga/node_name.rb +90 -0
  65. data/lib/droonga/node_role.rb +72 -0
  66. data/lib/droonga/path.rb +34 -9
  67. data/lib/droonga/planner.rb +73 -7
  68. data/lib/droonga/plugin/async_command.rb +154 -0
  69. data/lib/droonga/plugins/catalog.rb +1 -0
  70. data/lib/droonga/plugins/crud.rb +22 -6
  71. data/lib/droonga/plugins/dump.rb +66 -135
  72. data/lib/droonga/plugins/groonga/delete.rb +13 -0
  73. data/lib/droonga/plugins/search/distributed_search_planner.rb +4 -4
  74. data/lib/droonga/plugins/system.rb +5 -26
  75. data/lib/droonga/plugins/system/absorb_data.rb +405 -0
  76. data/lib/droonga/plugins/system/statistics.rb +71 -0
  77. data/lib/droonga/plugins/system/status.rb +53 -0
  78. data/lib/droonga/process_control_protocol.rb +3 -1
  79. data/lib/droonga/process_supervisor.rb +32 -15
  80. data/lib/droonga/reducer.rb +69 -0
  81. data/lib/droonga/safe_file_writer.rb +1 -1
  82. data/lib/droonga/serf.rb +207 -276
  83. data/lib/droonga/serf/agent.rb +228 -0
  84. data/lib/droonga/serf/command.rb +94 -0
  85. data/lib/droonga/serf/downloader.rb +120 -0
  86. data/lib/droonga/serf/remote_command.rb +348 -0
  87. data/lib/droonga/serf/tag.rb +56 -0
  88. data/lib/droonga/service_installation.rb +2 -2
  89. data/lib/droonga/session.rb +49 -1
  90. data/lib/droonga/single_step.rb +6 -11
  91. data/lib/droonga/single_step_definition.rb +32 -1
  92. data/lib/droonga/slice.rb +14 -9
  93. data/lib/droonga/supervisor.rb +27 -20
  94. data/lib/droonga/test/stub_handler_messenger.rb +2 -1
  95. data/lib/droonga/timestamp.rb +69 -0
  96. data/lib/droonga/worker_process_agent.rb +33 -15
  97. data/sample/cluster-state.json +8 -0
  98. data/sample/cluster/Rakefile +30 -6
  99. data/test/command/fixture/integer-key-table.jsons +11 -0
  100. data/test/command/fixture/string-key-table.jsons +11 -0
  101. data/test/command/run-test.rb +4 -0
  102. data/test/command/suite/add/error/invalid-integer.expected +3 -3
  103. data/test/command/suite/add/error/invalid-time.expected +3 -3
  104. data/test/command/suite/add/{minimum.expected → key-integer.expected} +0 -0
  105. data/test/command/suite/add/{minimum.test → key-integer.test} +0 -0
  106. data/test/command/suite/add/key-string.expected +6 -0
  107. data/test/command/suite/add/key-string.test +9 -0
  108. data/test/command/suite/add/mismatched-key-type/acceptable/integer-for-string.expected +6 -0
  109. data/test/command/suite/add/mismatched-key-type/acceptable/integer-for-string.test +9 -0
  110. data/test/command/suite/add/mismatched-key-type/acceptable/string-for-integer.expected +6 -0
  111. data/test/command/suite/add/mismatched-key-type/acceptable/string-for-integer.test +9 -0
  112. data/test/command/suite/add/without-values.expected +6 -0
  113. data/test/command/suite/add/without-values.test +11 -0
  114. data/test/command/suite/dump/column/index.expected +33 -1
  115. data/test/command/suite/dump/column/index.test +1 -0
  116. data/test/command/suite/dump/column/scalar.expected +29 -1
  117. data/test/command/suite/dump/column/scalar.test +1 -0
  118. data/test/command/suite/dump/column/vector.expected +29 -1
  119. data/test/command/suite/dump/column/vector.test +1 -0
  120. data/test/command/suite/dump/record/scalar.catalog.json +12 -0
  121. data/test/command/suite/dump/record/scalar.expected +84 -0
  122. data/test/command/suite/dump/record/scalar.test +16 -0
  123. data/test/command/suite/dump/record/vector/reference.expected +83 -1
  124. data/test/command/suite/dump/record/vector/reference.test +1 -0
  125. data/test/command/suite/dump/table/array.expected +27 -1
  126. data/test/command/suite/dump/table/array.test +1 -0
  127. data/test/command/suite/dump/table/double_array_trie.expected +27 -1
  128. data/test/command/suite/dump/table/double_array_trie.test +1 -0
  129. data/test/command/suite/dump/table/hash.expected +27 -1
  130. data/test/command/suite/dump/table/hash.test +1 -0
  131. data/test/command/suite/dump/table/patricia_trie.expected +27 -1
  132. data/test/command/suite/dump/table/patricia_trie.test +1 -0
  133. data/test/command/suite/groonga/delete/{success.expected → key-integer.expected} +0 -0
  134. data/test/command/suite/groonga/delete/key-integer.test +17 -0
  135. data/test/command/suite/groonga/delete/key-string.expected +19 -0
  136. data/test/command/suite/groonga/delete/{success.test → key-string.test} +4 -6
  137. data/test/command/suite/groonga/delete/mismatched-type-key/acceptable/integer-for-string.expected +19 -0
  138. data/test/command/suite/groonga/delete/mismatched-type-key/acceptable/integer-for-string.test +17 -0
  139. data/test/command/suite/groonga/delete/mismatched-type-key/acceptable/string-for-integer.expected +19 -0
  140. data/test/command/suite/groonga/delete/mismatched-type-key/acceptable/string-for-integer.test +17 -0
  141. data/test/command/suite/message/error/missing-dataset.test +1 -0
  142. data/test/command/suite/system/absorb-data/records.catalog.json +58 -0
  143. data/test/command/suite/system/absorb-data/records.expected +32 -0
  144. data/test/command/suite/system/absorb-data/records.test +24 -0
  145. data/test/command/suite/system/statistics/object/count/empty.expected +11 -0
  146. data/test/command/suite/system/statistics/object/count/empty.test +12 -0
  147. data/test/command/suite/system/statistics/object/count/per-volume/empty.catalog.json +36 -0
  148. data/test/command/suite/system/statistics/object/count/per-volume/empty.expected +19 -0
  149. data/test/command/suite/system/statistics/object/count/per-volume/empty.test +12 -0
  150. data/test/command/suite/system/statistics/object/count/per-volume/record.catalog.json +40 -0
  151. data/test/command/suite/system/statistics/object/count/per-volume/record.expected +19 -0
  152. data/test/command/suite/system/statistics/object/count/per-volume/record.test +23 -0
  153. data/test/command/suite/system/statistics/object/count/per-volume/schema.catalog.json +40 -0
  154. data/test/command/suite/system/statistics/object/count/per-volume/schema.expected +19 -0
  155. data/test/command/suite/system/statistics/object/count/per-volume/schema.test +13 -0
  156. data/test/command/suite/system/statistics/object/count/record.catalog.json +12 -0
  157. data/test/command/suite/system/statistics/object/count/record.expected +11 -0
  158. data/test/command/suite/system/statistics/object/count/record.test +23 -0
  159. data/test/command/suite/system/statistics/object/count/schema.catalog.json +12 -0
  160. data/test/command/suite/system/statistics/object/count/schema.expected +11 -0
  161. data/test/command/suite/system/statistics/object/count/schema.test +13 -0
  162. data/test/command/suite/system/status.expected +3 -2
  163. data/test/unit/catalog/test_dataset.rb +4 -1
  164. data/test/unit/{test_catalog_generator.rb → catalog/test_generator.rb} +2 -2
  165. data/test/unit/catalog/test_replicas_volume.rb +79 -0
  166. data/test/unit/catalog/test_single_volume.rb +2 -2
  167. data/test/unit/catalog/test_slice.rb +33 -1
  168. data/test/unit/catalog/{test_collection_volume.rb → test_slices_volume.rb} +72 -11
  169. data/test/unit/catalog/test_version2.rb +3 -0
  170. data/test/unit/helper/distributed_search_planner_helper.rb +2 -2
  171. data/test/unit/plugins/catalog/test_fetch.rb +4 -4
  172. data/test/unit/plugins/crud/test_add.rb +44 -4
  173. data/test/unit/plugins/groonga/test_column_create.rb +4 -4
  174. data/test/unit/plugins/groonga/test_column_list.rb +4 -4
  175. data/test/unit/plugins/groonga/test_column_remove.rb +4 -4
  176. data/test/unit/plugins/groonga/test_column_rename.rb +4 -4
  177. data/test/unit/plugins/groonga/test_delete.rb +73 -10
  178. data/test/unit/plugins/groonga/test_table_create.rb +4 -4
  179. data/test/unit/plugins/groonga/test_table_list.rb +4 -4
  180. data/test/unit/plugins/groonga/test_table_remove.rb +4 -4
  181. data/test/unit/plugins/search/test_handler.rb +4 -4
  182. data/test/unit/plugins/search/test_planner.rb +4 -2
  183. data/test/unit/plugins/system/test_status.rb +31 -15
  184. data/test/unit/plugins/test_watch.rb +16 -16
  185. data/test/unit/test_address.rb +4 -4
  186. metadata +134 -35
  187. data/lib/droonga/catalog/volume_collection.rb +0 -79
  188. data/lib/droonga/catalog_fetcher.rb +0 -53
  189. data/lib/droonga/catalog_generator.rb +0 -243
  190. data/lib/droonga/catalog_loader.rb +0 -56
  191. data/lib/droonga/command/remote.rb +0 -404
  192. data/lib/droonga/data_absorber.rb +0 -264
  193. data/lib/droonga/node_status.rb +0 -71
  194. data/lib/droonga/serf_downloader.rb +0 -115
  195. data/test/unit/catalog/test_volume_collection.rb +0 -78
@@ -55,33 +55,31 @@ module Droonga
55
55
  end
56
56
  end
57
57
 
58
- def scatter(record, options={})
58
+ def scatter(options={})
59
59
  @processor = {
60
60
  "command" => @source_message["type"],
61
61
  "dataset" => @dataset.name,
62
62
  "body" => options[:body] || @source_message["body"],
63
- "record" => record,
63
+ "record" => options[:record],
64
64
  "type" => "scatter",
65
65
  "outputs" => [],
66
- "replica" => "all",
67
- "post" => true
66
+ "replica" => options[:replica] || "all",
67
+ "slice" => options[:slice] || "all",
68
+ "post" => options[:write] || false,
68
69
  }
69
70
  end
70
71
 
71
72
  def broadcast(options={})
72
- processor = {
73
+ @processor = {
73
74
  "command" => @source_message["type"],
74
75
  "dataset" => @dataset.name,
75
76
  "body" => options[:body] || @source_message["body"],
76
77
  "type" => "broadcast",
77
78
  "outputs" => [],
78
- "replica" => "random"
79
+ "replica" => options[:replica] || "random",
80
+ "slice" => options[:slice] || "all",
81
+ "post" => options[:write] || false,
79
82
  }
80
- if options[:write]
81
- processor["replica"] = "all"
82
- processor["post"] = true
83
- end
84
- @processor = processor
85
83
  end
86
84
 
87
85
  private
@@ -1,6 +1,6 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  #
3
- # Copyright (C) 2013-2014 Droonga Project
3
+ # Copyright (C) 2013-2015 Droonga Project
4
4
  #
5
5
  # This library is free software; you can redistribute it and/or
6
6
  # modify it under the terms of the GNU Lesser General Public
@@ -15,100 +15,123 @@
15
15
  # License along with this library; if not, write to the Free Software
16
16
  # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
17
 
18
- require "time"
19
18
  require "fileutils"
19
+ require "time"
20
+
20
21
  require "droonga/engine/version"
21
22
  require "droonga/loggable"
23
+ require "droonga/deferrable"
22
24
  require "droonga/engine_state"
23
- require "droonga/catalog_loader"
25
+ require "droonga/cluster"
26
+ require "droonga/catalog/loader"
24
27
  require "droonga/dispatcher"
25
- require "droonga/file_observer"
26
- require "droonga/live_nodes_list_loader"
27
- require "droonga/node_status"
28
+ require "droonga/serf"
29
+ require "droonga/serf/tag"
30
+ require "droonga/timestamp"
28
31
 
29
32
  module Droonga
30
33
  class Engine
31
34
  include Loggable
35
+ include Deferrable
32
36
 
33
- attr_writer :on_ready
34
- def initialize(loop, name, internal_name)
35
- @state = EngineState.new(loop, name, internal_name)
37
+ attr_reader :cluster
38
+
39
+ def initialize(loop, name, internal_name, options={})
40
+ @name = name
41
+ @internal_name = internal_name
42
+ @loop = loop
36
43
  @catalog = load_catalog
37
- @state.catalog = @catalog
44
+ @state = EngineState.new(loop, name,
45
+ internal_name,
46
+ :catalog => @catalog,
47
+ :internal_connection_lifetime =>
48
+ options[:internal_connection_lifetime])
49
+ @cluster = Cluster.new(loop,
50
+ :catalog => @catalog,
51
+ :internal_connection_lifetime =>
52
+ options[:internal_connection_lifetime])
53
+
38
54
  @dispatcher = create_dispatcher
39
- @live_nodes_list_observer = FileObserver.new(loop, Path.live_nodes)
40
- @live_nodes_list_observer.on_change = lambda do
41
- @state.live_nodes = load_live_nodes
42
- end
43
- @node_status_observer = FileObserver.new(loop, Path.node_status)
44
- @node_status_observer.on_change = lambda do
45
- logger.trace("reloading node_status: start")
46
- node_status.reload
47
- logger.trace("reloading node_status: done")
55
+ @cluster.on_change = lambda do
56
+ @dispatcher.refresh_node_reference
48
57
  end
49
- @on_ready = nil
50
58
  end
51
59
 
52
60
  def start
53
61
  logger.trace("start: start")
54
62
  @state.on_ready = lambda do
55
- @on_ready.call if @on_ready
63
+ on_ready
64
+ serf = Serf.new(@name.to_s)
65
+ serf.set_tag(Serf::Tag.internal_node_name, @internal_name)
66
+ end
67
+ @state.on_failure = lambda do
68
+ on_failure
56
69
  end
57
70
  @state.start
58
- @live_nodes_list_observer.start
59
- @node_status_observer.start
71
+ @cluster.start
60
72
  @dispatcher.start
73
+ @last_message_timestamp_observer = run_last_message_timestamp_observer
61
74
  logger.trace("start: done")
62
75
  end
63
76
 
64
77
  def stop_gracefully
65
78
  logger.trace("stop_gracefully: start")
66
- @live_nodes_list_observer.stop
67
- @node_status_observer.stop
79
+ @last_message_timestamp_observer.stop
80
+ Timestamp.last_message_timestamp = nil # to avoid old timestamp is used
81
+ @cluster.shutdown
68
82
  on_finish = lambda do
69
- logger.trace("stop_gracefully/on_finish: start")
70
- save_last_processed_message_timestamp
83
+ logger.trace("stop_gracefully: middle")
71
84
  @dispatcher.stop_gracefully do
85
+ #XXX We must save last processed message timstamp
86
+ # based on forwarded/dispatched messages while
87
+ # "graceful stop" operations.
88
+ save_last_message_timestamp
72
89
  @state.shutdown
73
90
  yield
91
+ logger.trace("stop_gracefully: done")
74
92
  end
75
- logger.trace("stop_gracefully/on_finish: done")
76
93
  end
77
94
  if @state.have_session?
78
- logger.trace("stop_gracefully/having sessions")
95
+ logger.trace("stop_gracefully: having sessions")
79
96
  @state.on_finish = on_finish
80
97
  else
81
- logger.trace("stop_gracefully/no session")
98
+ logger.trace("stop_gracefully: no session")
82
99
  on_finish.call
83
100
  end
84
- logger.trace("stop_gracefully: done")
85
101
  end
86
102
 
87
103
  # It may be called after stop_gracefully.
88
104
  def stop_immediately
89
105
  logger.trace("stop_immediately: start")
90
- save_last_processed_message_timestamp
91
- @live_nodes_list_observer.stop
92
- @node_status_observer.stop
106
+ @last_message_timestamp_observer.stop
107
+ Timestamp.last_message_timestamp = nil # to avoid old timestamp is used
93
108
  @dispatcher.stop_immediately
109
+ save_last_message_timestamp
110
+ @cluster.shutdown
94
111
  @state.shutdown
95
112
  logger.trace("stop_immediately: done")
96
113
  end
97
114
 
98
- def process(message)
99
- return unless effective_message?(message)
100
- @last_processed_message_timestamp = message["date"]
101
- @dispatcher.process_message(message)
115
+ def refresh_self_reference
116
+ @cluster.refresh_connection_for(@name)
117
+ @state.forwarder.refresh_connection_for(@name)
102
118
  end
103
119
 
104
- def node_status
105
- @node_status ||= NodeStatus.new
120
+ def process(message)
121
+ if message.include?("date")
122
+ date = Time.parse(message["date"])
123
+ if @last_message_timestamp.nil? or
124
+ @last_message_timestamp < date
125
+ @last_message_timestamp = date
126
+ end
127
+ end
128
+ @dispatcher.process_message(message)
106
129
  end
107
130
 
108
131
  private
109
132
  def load_catalog
110
133
  catalog_path = Path.catalog
111
- loader = CatalogLoader.new(catalog_path.to_s)
134
+ loader = Catalog::Loader.new(catalog_path.to_s)
112
135
  catalog = loader.load
113
136
  logger.info("catalog loaded",
114
137
  :path => catalog_path.to_s,
@@ -116,48 +139,30 @@ module Droonga
116
139
  catalog
117
140
  end
118
141
 
119
- def load_live_nodes
120
- path = Path.live_nodes
121
- loader = LiveNodesListLoader.new(path)
122
- live_nodes = loader.load
123
- logger.info("live-nodes loaded",
124
- :path => path.to_s,
125
- :mtime => path.mtime)
126
- live_nodes
127
- end
128
-
129
142
  def create_dispatcher
130
- Dispatcher.new(@state, @catalog)
131
- end
132
-
133
- def save_last_processed_message_timestamp
134
- logger.trace("output_last_processed_message_timestamp: start")
135
- node_status.set(:last_processed_message_timestamp, @last_processed_message_timestamp.to_s)
136
- logger.trace("output_last_processed_message_timestamp: done")
143
+ Dispatcher.new(@state, @cluster, @catalog)
137
144
  end
138
145
 
139
- def effective_message?(message)
140
- effective_timestamp = effective_message_timestamp
141
- return true if effective_timestamp.nil?
142
-
143
- message_timestamp = Time.parse(message["date"])
144
- logger.trace("checking effective_message_timestamp (#{effective_timestamp}) vs message_timestamp(message_timestamp)")
145
- return false if effective_timestamp >= message_timestamp
146
-
147
- logger.trace("deleting obsolete effective_message_timestamp: start")
148
- node_status.delete(:effective_message_timestamp)
149
- logger.trace("deleting obsolete effective_message_timestamp: done")
150
- true
146
+ def save_last_message_timestamp
147
+ logger.trace("save_last_message_timestamp: start",
148
+ :current => @last_message_timestamp)
149
+ Timestamp.last_message_timestamp = @last_message_timestamp
150
+ logger.trace("save_last_message_timestamp: done")
151
151
  end
152
152
 
153
- def effective_message_timestamp
154
- timestamp = node_status.get(:effective_message_timestamp)
155
- return nil unless timestamp
156
-
157
- begin
158
- Time.parse(timestamp)
159
- rescue ArgumentError
160
- nil
153
+ def run_last_message_timestamp_observer
154
+ Timestamp.run_last_message_timestamp_observer(@loop) do |timestamp|
155
+ logger.trace("last message timestamp file is modified",
156
+ :loaded => timestamp,
157
+ :current => @last_message_timestamp)
158
+ if timestamp
159
+ if @last_message_timestamp.nil? or
160
+ timestamp > @last_message_timestamp
161
+ @last_message_timestamp = timestamp
162
+ elsif timestamp < @last_message_timestamp
163
+ Timestamp.last_message_timestamp = @last_message_timestamp
164
+ end
165
+ end
161
166
  end
162
167
  end
163
168
 
@@ -15,6 +15,6 @@
15
15
 
16
16
  module Droonga
17
17
  class Engine
18
- VERSION = "1.0.9"
18
+ VERSION = "1.1.0"
19
19
  end
20
20
  end
@@ -0,0 +1,301 @@
1
+ # Copyright (C) 2015 Droonga Project
2
+ #
3
+ # This library is free software; you can redistribute it and/or
4
+ # modify it under the terms of the GNU Lesser General Public
5
+ # License version 2.1 as published by the Free Software Foundation.
6
+ #
7
+ # This library is distributed in the hope that it will be useful,
8
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
9
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10
+ # Lesser General Public License for more details.
11
+ #
12
+ # You should have received a copy of the GNU Lesser General Public
13
+ # License along with this library; if not, write to the Free Software
14
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
15
+
16
+ require "time"
17
+ require "coolio"
18
+
19
+ require "droonga/loggable"
20
+ require "droonga/forward_buffer"
21
+ require "droonga/fluent_message_sender"
22
+ require "droonga/node_name"
23
+ require "droonga/node_role"
24
+
25
+ module Droonga
26
+ class EngineNode
27
+ include Loggable
28
+
29
+ DEFAULT_AUTO_CLOSE_TIMEOUT_SECONDS = 60
30
+
31
+ attr_reader :name
32
+
33
+ def initialize(loop, name, state, options={})
34
+ @loop = loop
35
+ @name = name
36
+ @state = state
37
+ logger.trace("initialize: start")
38
+
39
+ @buffer = ForwardBuffer.new(name)
40
+ boundary_timestamp = accept_messages_newer_than_timestamp
41
+ @buffer.process_messages_newer_than(boundary_timestamp)
42
+ @buffer.on_forward = lambda do |message, destination|
43
+ output(message, destination)
44
+ end
45
+
46
+ @node_name = NodeName.parse(@name)
47
+
48
+ @sender = nil
49
+ @auto_close_timer = nil
50
+ @auto_close_timeout = options[:auto_close_timeout] ||
51
+ DEFAULT_AUTO_CLOSE_TIMEOUT_SECONDS
52
+
53
+ logger.trace("initialize: done")
54
+ end
55
+
56
+ def start
57
+ logger.trace("start: start")
58
+ resume
59
+ logger.trace("start: done")
60
+ end
61
+
62
+ def shutdown
63
+ logger.trace("shutdown: start")
64
+ if @sender
65
+ @sender.shutdown
66
+ @sender = nil
67
+ end
68
+ if @auto_close_timer
69
+ @auto_close_timer.detach
70
+ @auto_close_timer = nil
71
+ end
72
+ logger.trace("shutdown: done")
73
+ end
74
+
75
+ def refresh_connection
76
+ logger.trace("refresh_connection: start")
77
+ shutdown
78
+ sender # instantiate new sender
79
+ logger.trace("refresh_connection: done")
80
+ end
81
+
82
+ def forward(message, destination)
83
+ if read_message?(message)
84
+ # A node can receive read messages for other nodes,
85
+ # while changing its role. They must not be buffered.
86
+ output(message, destination)
87
+ return
88
+ end
89
+
90
+ unless really_writable?
91
+ # The target node is not ready. We should send the message later.
92
+ @buffer.add(message, destination)
93
+ return
94
+ end
95
+
96
+ # The target node is ready.
97
+ if @buffer.empty?
98
+ output(message, destination)
99
+ else
100
+ @buffer.add(message, destination)
101
+ @buffer.start_forward
102
+ end
103
+ end
104
+
105
+ def bounce(message)
106
+ destination = {
107
+ "to" => name,
108
+ "type" => message["type"],
109
+ }
110
+ output(message, destination)
111
+ end
112
+
113
+ def role
114
+ if @state
115
+ @state["role"]
116
+ else
117
+ NodeRole::SERVICE_PROVIDER
118
+ end
119
+ end
120
+
121
+ def live?
122
+ @state.nil? or @state["live"]
123
+ end
124
+
125
+ def forwardable?
126
+ return false unless live?
127
+ role == NodeRole.mine
128
+ end
129
+
130
+ def readable?
131
+ forwardable? and @buffer.empty? and
132
+ (complete_service_provider? or not service_provider?)
133
+ end
134
+
135
+ def writable?
136
+ case NodeRole.mine
137
+ when NodeRole::SERVICE_PROVIDER
138
+ true
139
+ when NodeRole::ABSORB_SOURCE
140
+ absorb_source?
141
+ when NodeRole::ABSORB_DESTINATION
142
+ absorb_destination?
143
+ else
144
+ false
145
+ end
146
+ end
147
+
148
+ def status
149
+ if readable?
150
+ "active"
151
+ elsif forwardable?
152
+ "inactive"
153
+ elsif dead?
154
+ "dead"
155
+ else
156
+ "inactive"
157
+ end
158
+ end
159
+
160
+ def to_json
161
+ {
162
+ "name" => name,
163
+ "role" => role,
164
+ "live" => live?,
165
+ "status" => status,
166
+ }
167
+ end
168
+
169
+ def resume
170
+ logger.trace("resume: start")
171
+ sender.resume
172
+ unless @buffer.empty?
173
+ if really_writable?
174
+ logger.info("Target becomes writable. Start to forwarding.")
175
+ @buffer.start_forward
176
+ else
177
+ logger.info("Target is still unwritable.")
178
+ end
179
+ end
180
+ logger.trace("resume: done")
181
+ end
182
+
183
+ private
184
+ def parse_node_name(name)
185
+ unless name =~ /\A(.*):(\d+)\/([^.]+)\z/
186
+ raise "name format: hostname:port/tag"
187
+ end
188
+ {
189
+ :host => $1,
190
+ :port => $2,
191
+ :tag => $3,
192
+ }
193
+ end
194
+
195
+ def have_unprocessed_messages?
196
+ @state and @state["have_unprocessed_messages"]
197
+ end
198
+
199
+ def accept_messages_newer_than_timestamp
200
+ @accept_messages_newer_than_timestamp ||= parse_accept_messages_newer_than_timestamp
201
+ end
202
+
203
+ def parse_accept_messages_newer_than_timestamp
204
+ return nil if @state.nil? or @state["accept_messages_newer_than"].nil?
205
+ Time.parse(@state["accept_messages_newer_than"])
206
+ end
207
+
208
+ def dead?
209
+ not live?
210
+ end
211
+
212
+ def service_provider?
213
+ role == NodeRole::SERVICE_PROVIDER
214
+ end
215
+
216
+ def absorb_source?
217
+ role == NodeRole::ABSORB_SOURCE
218
+ end
219
+
220
+ def absorb_destination?
221
+ role == NodeRole::ABSORB_DESTINATION
222
+ end
223
+
224
+ def complete_service_provider?
225
+ service_provider? and not have_unprocessed_messages?
226
+ end
227
+
228
+ def really_writable?
229
+ return false unless writable?
230
+ case NodeRole.mine
231
+ when NodeRole::SERVICE_PROVIDER
232
+ service_provider?
233
+ when NodeRole::ABSORB_SOURCE
234
+ not absorb_destination?
235
+ else
236
+ true
237
+ end
238
+ end
239
+
240
+ def read_message?(message)
241
+ steps = message["body"]["steps"]
242
+ return false unless steps
243
+ steps.all? do |step|
244
+ not step["write"]
245
+ end
246
+ end
247
+
248
+ def output(message, destination)
249
+ command = destination["type"]
250
+ receiver = destination["to"]
251
+ arguments = destination["arguments"]
252
+ parsed_receiver = parse_node_name(receiver)
253
+
254
+ override_message = {
255
+ "type" => command,
256
+ }
257
+ override_message["arguments"] = arguments if arguments
258
+ message = message.merge(override_message)
259
+ output_tag = "#{parsed_receiver[:tag]}.message"
260
+ log_info = "<#{receiver}>:<#{output_tag}>"
261
+ logger.trace("forward: start: #{log_info}")
262
+ sender.send(output_tag, message)
263
+ set_auto_close_timer
264
+ logger.trace("forward: end")
265
+ end
266
+
267
+ def sender
268
+ @sender ||= create_sender
269
+ end
270
+
271
+ def create_sender
272
+ sender = FluentMessageSender.new(@loop,
273
+ @node_name.host,
274
+ @node_name.port,
275
+ :buffering => true)
276
+ sender.start
277
+ sender
278
+ end
279
+
280
+ def set_auto_close_timer
281
+ previous_timer = @auto_close_timer
282
+ previous_timer.detach if previous_timer
283
+
284
+ @auto_close_timer = Coolio::TimerWatcher.new(@auto_close_timeout)
285
+ @auto_close_timer.on_timer do
286
+ @auto_close_timer.detach
287
+ @auto_close_timer = nil
288
+ if @sender
289
+ logger.info("sender for #{name} is automatically closed by timeout.")
290
+ @sender.shutdown
291
+ @sender = nil
292
+ end
293
+ end
294
+ @loop.attach(@auto_close_timer)
295
+ end
296
+
297
+ def log_tag
298
+ "[#{Process.ppid}] engine-node: #{@name}"
299
+ end
300
+ end
301
+ end