droonga-engine 1.0.5 → 1.0.6
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.
- checksums.yaml +4 -4
- data/bin/droonga-engine-absorb-data +2 -1
- data/bin/droonga-engine-catalog-generate +21 -5
- data/bin/droonga-engine-catalog-modify +22 -6
- data/bin/droonga-engine-configure +215 -0
- data/bin/droonga-engine-join +48 -123
- data/bin/droonga-engine-unjoin +14 -1
- data/doc/text/news.md +21 -0
- data/droonga-engine.gemspec +12 -10
- data/install/centos/droonga-engine +60 -0
- data/install/centos/functions.sh +35 -0
- data/install/debian/droonga-engine +155 -0
- data/install/debian/functions.sh +33 -0
- data/install.sh +360 -0
- data/lib/droonga/address.rb +3 -1
- data/lib/droonga/catalog/dataset.rb +2 -0
- data/lib/droonga/catalog/version1.rb +16 -3
- data/lib/droonga/catalog/version2.rb +16 -3
- data/lib/droonga/catalog_fetcher.rb +51 -0
- data/lib/droonga/catalog_generator.rb +6 -5
- data/lib/droonga/catalog_modifier.rb +45 -0
- data/lib/droonga/command/droonga_engine.rb +96 -29
- data/lib/droonga/command/droonga_engine_service.rb +5 -0
- data/lib/droonga/command/remote.rb +368 -0
- data/lib/droonga/command/serf_event_handler.rb +37 -304
- data/lib/droonga/dispatcher.rb +15 -1
- data/lib/droonga/engine/version.rb +1 -1
- data/lib/droonga/engine.rb +11 -4
- data/lib/droonga/engine_state.rb +2 -0
- data/lib/droonga/farm.rb +14 -5
- data/lib/droonga/fluent_message_receiver.rb +23 -6
- data/lib/droonga/fluent_message_sender.rb +5 -1
- data/lib/droonga/node_status.rb +67 -0
- data/lib/droonga/path.rb +28 -4
- data/lib/droonga/plugins/catalog.rb +40 -0
- data/lib/droonga/safe_file_writer.rb +1 -1
- data/lib/droonga/searcher.rb +3 -15
- data/lib/droonga/serf.rb +17 -32
- data/lib/droonga/serf_downloader.rb +26 -1
- data/lib/droonga/service_installation.rb +123 -0
- data/lib/droonga/session.rb +4 -0
- data/lib/droonga/slice.rb +22 -12
- data/lib/droonga/supervisor.rb +16 -2
- data/lib/droonga/worker_process_agent.rb +13 -1
- data/sample/droonga-engine.yaml +5 -0
- data/test/command/config/default/catalog.json +1 -1
- data/test/command/config/default/droonga-engine.yaml +4 -0
- data/test/command/config/version1/catalog.json +1 -1
- data/test/command/suite/catalog/fetch.expected +64 -0
- data/test/command/suite/catalog/fetch.test +6 -0
- data/test/unit/catalog/test_version1.rb +2 -2
- data/test/unit/catalog/test_version2.rb +3 -3
- data/test/unit/helper/sandbox.rb +3 -1
- data/test/unit/plugins/catalog/test_fetch.rb +76 -0
- data/test/unit/test_catalog_generator.rb +7 -3
- metadata +74 -27
- data/bin/droonga-engine-data-publisher +0 -66
@@ -0,0 +1,368 @@
|
|
1
|
+
# Copyright (C) 2014 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 "json"
|
17
|
+
|
18
|
+
require "droonga/path"
|
19
|
+
require "droonga/serf"
|
20
|
+
require "droonga/node_status"
|
21
|
+
require "droonga/catalog_generator"
|
22
|
+
require "droonga/catalog_modifier"
|
23
|
+
require "droonga/catalog_fetcher"
|
24
|
+
require "droonga/data_absorber"
|
25
|
+
require "droonga/safe_file_writer"
|
26
|
+
require "droonga/service_installation"
|
27
|
+
|
28
|
+
module Droonga
|
29
|
+
module Command
|
30
|
+
module Remote
|
31
|
+
class Base
|
32
|
+
attr_reader :response
|
33
|
+
|
34
|
+
def initialize(serf_name, params)
|
35
|
+
@serf_name = serf_name
|
36
|
+
@params = params
|
37
|
+
@response = {
|
38
|
+
"log" => []
|
39
|
+
}
|
40
|
+
@serf = Serf.new(nil, @serf_name)
|
41
|
+
|
42
|
+
@service_installation = ServiceInstallation.new
|
43
|
+
@service_installation.ensure_using_service_base_directory
|
44
|
+
|
45
|
+
log("params = #{params}")
|
46
|
+
end
|
47
|
+
|
48
|
+
def process
|
49
|
+
# override me!
|
50
|
+
end
|
51
|
+
|
52
|
+
def should_process?
|
53
|
+
for_me? or @params.nil? or not @params.include?("node")
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
def node
|
58
|
+
@serf_name
|
59
|
+
end
|
60
|
+
|
61
|
+
def host
|
62
|
+
node.split(":").first
|
63
|
+
end
|
64
|
+
|
65
|
+
def target_node
|
66
|
+
@params && @params["node"]
|
67
|
+
end
|
68
|
+
|
69
|
+
def for_me?
|
70
|
+
target_node == @serf_name
|
71
|
+
end
|
72
|
+
|
73
|
+
def log(message)
|
74
|
+
@response["log"] << message
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
class ChangeRole < Base
|
79
|
+
def process
|
80
|
+
status = NodeStatus.new
|
81
|
+
status.set(:role, @params["role"])
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
class ReportStatus < Base
|
86
|
+
def process
|
87
|
+
status = NodeStatus.new
|
88
|
+
@response["value"] = status.get(@params["key"])
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
class Join < Base
|
93
|
+
def process
|
94
|
+
log("type = #{type}")
|
95
|
+
case type
|
96
|
+
when "replica"
|
97
|
+
join_as_replica
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
def type
|
103
|
+
@params["type"]
|
104
|
+
end
|
105
|
+
|
106
|
+
def source_node
|
107
|
+
@params["source"]
|
108
|
+
end
|
109
|
+
|
110
|
+
def joining_node
|
111
|
+
@params["node"]
|
112
|
+
end
|
113
|
+
|
114
|
+
def dataset_name
|
115
|
+
@params["dataset"]
|
116
|
+
end
|
117
|
+
|
118
|
+
def valid_params?
|
119
|
+
have_required_params? and
|
120
|
+
valid_node?(source_node) and
|
121
|
+
valid_node?(joining_node)
|
122
|
+
end
|
123
|
+
|
124
|
+
def have_required_params?
|
125
|
+
required_params = [
|
126
|
+
source_node,
|
127
|
+
joining_node,
|
128
|
+
dataset_name,
|
129
|
+
]
|
130
|
+
required_params.all? do |param|
|
131
|
+
not param.nil?
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
NODE_PATTERN = /\A([^:]+):(\d+)\/(.+)\z/
|
136
|
+
|
137
|
+
def valid_node?(node)
|
138
|
+
node =~ NODE_PATTERN
|
139
|
+
end
|
140
|
+
|
141
|
+
def source_host
|
142
|
+
@source_host ||= (source_node =~ NODE_PATTERN && $1)
|
143
|
+
end
|
144
|
+
|
145
|
+
def joining_host
|
146
|
+
@joining_host ||= (joining_node =~ NODE_PATTERN && $1)
|
147
|
+
end
|
148
|
+
|
149
|
+
def port
|
150
|
+
@port ||= (source_node =~ NODE_PATTERN && $2 && $2.to_i)
|
151
|
+
end
|
152
|
+
|
153
|
+
def tag
|
154
|
+
@tag ||= (source_node =~ NODE_PATTERN && $3)
|
155
|
+
end
|
156
|
+
|
157
|
+
def should_absorb_data?
|
158
|
+
@params["copy"]
|
159
|
+
end
|
160
|
+
|
161
|
+
def join_as_replica
|
162
|
+
return unless valid_params?
|
163
|
+
|
164
|
+
log("source_node = #{source_node}")
|
165
|
+
|
166
|
+
@catalog = fetch_catalog
|
167
|
+
|
168
|
+
other_hosts = replica_hosts
|
169
|
+
log("other_hosts = #{other_hosts}")
|
170
|
+
return if other_hosts.empty?
|
171
|
+
|
172
|
+
# restart self with the fetched catalog.
|
173
|
+
SafeFileWriter.write(Path.catalog) do |output, file|
|
174
|
+
output.puts(JSON.pretty_generate(@catalog))
|
175
|
+
@service_installation.ensure_correct_file_permission(file)
|
176
|
+
end
|
177
|
+
|
178
|
+
absorb_data if should_absorb_data?
|
179
|
+
|
180
|
+
log("joining to the cluster: update myself")
|
181
|
+
|
182
|
+
CatalogModifier.modify do |modifier, file|
|
183
|
+
modifier.datasets[dataset_name].replicas.hosts += other_hosts
|
184
|
+
modifier.datasets[dataset_name].replicas.hosts.uniq!
|
185
|
+
@service_installation.ensure_correct_file_permission(file)
|
186
|
+
end
|
187
|
+
|
188
|
+
@serf.join(*other_hosts)
|
189
|
+
end
|
190
|
+
|
191
|
+
def replica_hosts
|
192
|
+
generator = CatalogGenerator.new
|
193
|
+
generator.load(@catalog)
|
194
|
+
dataset = generator.dataset_for_host(source_host) ||
|
195
|
+
generator.dataset_for_host(host)
|
196
|
+
return [] unless dataset
|
197
|
+
dataset.replicas.hosts
|
198
|
+
end
|
199
|
+
|
200
|
+
def fetch_catalog
|
201
|
+
fetcher = CatalogFetcher.new(:host => source_host,
|
202
|
+
:port => port,
|
203
|
+
:tag => tag,
|
204
|
+
:receiver_host => host)
|
205
|
+
fetcher.fetch(:dataset => dataset_name)
|
206
|
+
end
|
207
|
+
|
208
|
+
def absorb_data
|
209
|
+
log("starting to copy data from #{source_host}")
|
210
|
+
|
211
|
+
CatalogModifier.modify do |modifier, file|
|
212
|
+
modifier.datasets[dataset_name].replicas.hosts = [host]
|
213
|
+
@service_installation.ensure_correct_file_permission(file)
|
214
|
+
end
|
215
|
+
sleep(5) #TODO: wait for restart. this should be done more safely, to avoid starting of absorbing with old catalog.json.
|
216
|
+
|
217
|
+
status = NodeStatus.new
|
218
|
+
status.set(:absorbing, true)
|
219
|
+
DataAbsorber.absorb(:dataset => dataset_name,
|
220
|
+
:source_host => source_host,
|
221
|
+
:destination_host => joining_host,
|
222
|
+
:port => port,
|
223
|
+
:tag => tag)
|
224
|
+
status.delete(:absorbing)
|
225
|
+
sleep(1)
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
class AbsorbData < Base
|
230
|
+
attr_writer :dataset_name, :port, :tag
|
231
|
+
|
232
|
+
def process
|
233
|
+
return unless source
|
234
|
+
|
235
|
+
log("start to absorb data from #{source}")
|
236
|
+
|
237
|
+
if dataset_name.nil? or port.nil? or tag.nil?
|
238
|
+
current_catalog = JSON.parse(Path.catalog.read)
|
239
|
+
generator = CatalogGenerator.new
|
240
|
+
generator.load(current_catalog)
|
241
|
+
|
242
|
+
dataset = generator.dataset_for_host(source)
|
243
|
+
return unless dataset
|
244
|
+
|
245
|
+
self.dataset_name = dataset.name
|
246
|
+
self.port = dataset.replicas.port
|
247
|
+
self.tag = dataset.replicas.tag
|
248
|
+
end
|
249
|
+
|
250
|
+
log("dataset = #{dataset_name}")
|
251
|
+
log("port = #{port}")
|
252
|
+
log("tag = #{tag}")
|
253
|
+
|
254
|
+
status = NodeStatus.new
|
255
|
+
status.set(:absorbing, true)
|
256
|
+
DataAbsorber.absorb(:dataset => dataset_name,
|
257
|
+
:source_host => source,
|
258
|
+
:destination_host => host,
|
259
|
+
:port => port,
|
260
|
+
:tag => tag,
|
261
|
+
:client => "droonga-send")
|
262
|
+
status.delete(:absorbing)
|
263
|
+
end
|
264
|
+
|
265
|
+
private
|
266
|
+
def source
|
267
|
+
@params["source"]
|
268
|
+
end
|
269
|
+
|
270
|
+
def dataset_name
|
271
|
+
@dataset_name ||= @params["dataset"]
|
272
|
+
end
|
273
|
+
|
274
|
+
def port
|
275
|
+
@port ||= @params["port"]
|
276
|
+
end
|
277
|
+
|
278
|
+
def tag
|
279
|
+
@tag ||= @params["tag"]
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
class ModifyReplicasBase < Base
|
284
|
+
private
|
285
|
+
def dataset
|
286
|
+
@params["dataset"]
|
287
|
+
end
|
288
|
+
|
289
|
+
def hosts
|
290
|
+
@hosts ||= prepare_hosts
|
291
|
+
end
|
292
|
+
|
293
|
+
def prepare_hosts
|
294
|
+
hosts = @params["hosts"]
|
295
|
+
return nil unless hosts
|
296
|
+
hosts = [hosts] if hosts.is_a?(String)
|
297
|
+
hosts
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
class SetReplicas < ModifyReplicasBase
|
302
|
+
def process
|
303
|
+
return if dataset.nil? or hosts.nil?
|
304
|
+
|
305
|
+
log("new replicas: #{hosts.join(",")}")
|
306
|
+
|
307
|
+
CatalogModifier.modify do |modifier, file|
|
308
|
+
modifier.datasets[dataset].replicas.hosts = hosts
|
309
|
+
@service_installation.ensure_correct_file_permission(file)
|
310
|
+
end
|
311
|
+
|
312
|
+
@serf.join(*hosts)
|
313
|
+
#XXX Now we should restart serf agent to remove unjoined nodes from the list of members...
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
class AddReplicas < ModifyReplicasBase
|
318
|
+
def process
|
319
|
+
return if dataset.nil? or hosts.nil?
|
320
|
+
|
321
|
+
added_hosts = hosts - [host]
|
322
|
+
log("adding replicas: #{added_hosts.join(",")}")
|
323
|
+
return if added_hosts.empty?
|
324
|
+
|
325
|
+
CatalogModifier.modify do |modifier, file|
|
326
|
+
modifier.datasets[dataset].replicas.hosts += added_hosts
|
327
|
+
modifier.datasets[dataset].replicas.hosts.uniq!
|
328
|
+
@service_installation.ensure_correct_file_permission(file)
|
329
|
+
end
|
330
|
+
|
331
|
+
@serf.join(*added_hosts)
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
class RemoveReplicas < ModifyReplicasBase
|
336
|
+
def process
|
337
|
+
return if dataset.nil? or hosts.nil?
|
338
|
+
|
339
|
+
log("removing replicas: #{hosts.join(",")}")
|
340
|
+
|
341
|
+
CatalogModifier.modify do |modifier, file|
|
342
|
+
modifier.datasets[dataset].replicas.hosts -= hosts
|
343
|
+
@service_installation.ensure_correct_file_permission(file)
|
344
|
+
end
|
345
|
+
|
346
|
+
#XXX Now we should restart serf agent to remove unjoined nodes from the list of members...
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
class UpdateLiveNodes < Base
|
351
|
+
def process
|
352
|
+
path = Path.live_nodes
|
353
|
+
nodes = live_nodes
|
354
|
+
file_contents = JSON.pretty_generate(nodes)
|
355
|
+
SafeFileWriter.write(path) do |output, file|
|
356
|
+
output.puts(file_contents)
|
357
|
+
@service_installation.ensure_correct_file_permission(file)
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
private
|
362
|
+
def live_nodes
|
363
|
+
@serf.live_nodes
|
364
|
+
end
|
365
|
+
end
|
366
|
+
end
|
367
|
+
end
|
368
|
+
end
|