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.
- checksums.yaml +4 -4
- data/.travis.yml +1 -0
- data/benchmark/timer-watcher/benchmark.rb +44 -0
- data/bin/droonga-engine-absorb-data +246 -187
- data/bin/droonga-engine-catalog-generate +12 -12
- data/bin/droonga-engine-catalog-modify +4 -4
- data/bin/droonga-engine-join +352 -171
- data/bin/droonga-engine-set-role +54 -0
- data/bin/droonga-engine-unjoin +107 -112
- data/droonga-engine.gemspec +3 -3
- data/install.sh +55 -36
- data/install/centos/functions.sh +2 -2
- data/install/debian/functions.sh +2 -2
- data/lib/droonga/address.rb +26 -24
- data/lib/droonga/buffered_tcp_socket.rb +65 -10
- data/lib/droonga/catalog/base.rb +9 -6
- data/lib/droonga/catalog/dataset.rb +17 -41
- data/lib/droonga/catalog/fetcher.rb +64 -0
- data/lib/droonga/catalog/generator.rb +245 -0
- data/lib/droonga/catalog/loader.rb +66 -0
- data/lib/droonga/{catalog_modifier.rb → catalog/modifier.rb} +11 -18
- data/lib/droonga/catalog/replicas_volume.rb +123 -0
- data/lib/droonga/catalog/schema.rb +37 -37
- data/lib/droonga/catalog/single_volume.rb +11 -3
- data/lib/droonga/catalog/slice.rb +10 -6
- data/lib/droonga/catalog/{collection_volume.rb → slices_volume.rb} +47 -11
- data/lib/droonga/catalog/version1.rb +47 -19
- data/lib/droonga/catalog/version2.rb +11 -10
- data/lib/droonga/catalog/version2_validator.rb +4 -4
- data/lib/droonga/catalog/volume.rb +17 -5
- data/lib/droonga/changable.rb +25 -0
- data/lib/droonga/cluster.rb +237 -0
- data/lib/droonga/collector_runner.rb +4 -0
- data/lib/droonga/collectors.rb +2 -1
- data/lib/droonga/collectors/recursive_sum.rb +26 -0
- data/lib/droonga/command/droonga_engine.rb +404 -127
- data/lib/droonga/command/droonga_engine_service.rb +47 -11
- data/lib/droonga/command/droonga_engine_worker.rb +21 -1
- data/lib/droonga/command/remote_command_base.rb +78 -0
- data/lib/droonga/command/serf_event_handler.rb +29 -20
- data/lib/droonga/data_absorber_client.rb +222 -0
- data/lib/droonga/database_scanner.rb +106 -0
- data/lib/droonga/{live_nodes_list_loader.rb → deferrable.rb} +11 -24
- data/lib/droonga/differ.rb +58 -0
- data/lib/droonga/dispatcher.rb +155 -32
- data/lib/droonga/distributed_command_planner.rb +9 -11
- data/lib/droonga/engine.rb +83 -78
- data/lib/droonga/engine/version.rb +1 -1
- data/lib/droonga/engine_node.rb +301 -0
- data/lib/droonga/engine_state.rb +62 -40
- data/lib/droonga/farm.rb +44 -5
- data/lib/droonga/file_observer.rb +16 -12
- data/lib/droonga/fluent_message_receiver.rb +98 -29
- data/lib/droonga/fluent_message_sender.rb +30 -23
- data/lib/droonga/forward_buffer.rb +160 -0
- data/lib/droonga/forwarder.rb +73 -40
- data/lib/droonga/handler.rb +7 -6
- data/lib/droonga/handler_messenger.rb +15 -6
- data/lib/droonga/handler_runner.rb +6 -1
- data/lib/droonga/internal_fluent_message_receiver.rb +28 -8
- data/lib/droonga/job_pusher.rb +10 -7
- data/lib/droonga/job_receiver.rb +6 -4
- data/lib/droonga/logger.rb +7 -1
- data/lib/droonga/node_name.rb +90 -0
- data/lib/droonga/node_role.rb +72 -0
- data/lib/droonga/path.rb +34 -9
- data/lib/droonga/planner.rb +73 -7
- data/lib/droonga/plugin/async_command.rb +154 -0
- data/lib/droonga/plugins/catalog.rb +1 -0
- data/lib/droonga/plugins/crud.rb +22 -6
- data/lib/droonga/plugins/dump.rb +66 -135
- data/lib/droonga/plugins/groonga/delete.rb +13 -0
- data/lib/droonga/plugins/search/distributed_search_planner.rb +4 -4
- data/lib/droonga/plugins/system.rb +5 -26
- data/lib/droonga/plugins/system/absorb_data.rb +405 -0
- data/lib/droonga/plugins/system/statistics.rb +71 -0
- data/lib/droonga/plugins/system/status.rb +53 -0
- data/lib/droonga/process_control_protocol.rb +3 -1
- data/lib/droonga/process_supervisor.rb +32 -15
- data/lib/droonga/reducer.rb +69 -0
- data/lib/droonga/safe_file_writer.rb +1 -1
- data/lib/droonga/serf.rb +207 -276
- data/lib/droonga/serf/agent.rb +228 -0
- data/lib/droonga/serf/command.rb +94 -0
- data/lib/droonga/serf/downloader.rb +120 -0
- data/lib/droonga/serf/remote_command.rb +348 -0
- data/lib/droonga/serf/tag.rb +56 -0
- data/lib/droonga/service_installation.rb +2 -2
- data/lib/droonga/session.rb +49 -1
- data/lib/droonga/single_step.rb +6 -11
- data/lib/droonga/single_step_definition.rb +32 -1
- data/lib/droonga/slice.rb +14 -9
- data/lib/droonga/supervisor.rb +27 -20
- data/lib/droonga/test/stub_handler_messenger.rb +2 -1
- data/lib/droonga/timestamp.rb +69 -0
- data/lib/droonga/worker_process_agent.rb +33 -15
- data/sample/cluster-state.json +8 -0
- data/sample/cluster/Rakefile +30 -6
- data/test/command/fixture/integer-key-table.jsons +11 -0
- data/test/command/fixture/string-key-table.jsons +11 -0
- data/test/command/run-test.rb +4 -0
- data/test/command/suite/add/error/invalid-integer.expected +3 -3
- data/test/command/suite/add/error/invalid-time.expected +3 -3
- data/test/command/suite/add/{minimum.expected → key-integer.expected} +0 -0
- data/test/command/suite/add/{minimum.test → key-integer.test} +0 -0
- data/test/command/suite/add/key-string.expected +6 -0
- data/test/command/suite/add/key-string.test +9 -0
- data/test/command/suite/add/mismatched-key-type/acceptable/integer-for-string.expected +6 -0
- data/test/command/suite/add/mismatched-key-type/acceptable/integer-for-string.test +9 -0
- data/test/command/suite/add/mismatched-key-type/acceptable/string-for-integer.expected +6 -0
- data/test/command/suite/add/mismatched-key-type/acceptable/string-for-integer.test +9 -0
- data/test/command/suite/add/without-values.expected +6 -0
- data/test/command/suite/add/without-values.test +11 -0
- data/test/command/suite/dump/column/index.expected +33 -1
- data/test/command/suite/dump/column/index.test +1 -0
- data/test/command/suite/dump/column/scalar.expected +29 -1
- data/test/command/suite/dump/column/scalar.test +1 -0
- data/test/command/suite/dump/column/vector.expected +29 -1
- data/test/command/suite/dump/column/vector.test +1 -0
- data/test/command/suite/dump/record/scalar.catalog.json +12 -0
- data/test/command/suite/dump/record/scalar.expected +84 -0
- data/test/command/suite/dump/record/scalar.test +16 -0
- data/test/command/suite/dump/record/vector/reference.expected +83 -1
- data/test/command/suite/dump/record/vector/reference.test +1 -0
- data/test/command/suite/dump/table/array.expected +27 -1
- data/test/command/suite/dump/table/array.test +1 -0
- data/test/command/suite/dump/table/double_array_trie.expected +27 -1
- data/test/command/suite/dump/table/double_array_trie.test +1 -0
- data/test/command/suite/dump/table/hash.expected +27 -1
- data/test/command/suite/dump/table/hash.test +1 -0
- data/test/command/suite/dump/table/patricia_trie.expected +27 -1
- data/test/command/suite/dump/table/patricia_trie.test +1 -0
- data/test/command/suite/groonga/delete/{success.expected → key-integer.expected} +0 -0
- data/test/command/suite/groonga/delete/key-integer.test +17 -0
- data/test/command/suite/groonga/delete/key-string.expected +19 -0
- data/test/command/suite/groonga/delete/{success.test → key-string.test} +4 -6
- data/test/command/suite/groonga/delete/mismatched-type-key/acceptable/integer-for-string.expected +19 -0
- data/test/command/suite/groonga/delete/mismatched-type-key/acceptable/integer-for-string.test +17 -0
- data/test/command/suite/groonga/delete/mismatched-type-key/acceptable/string-for-integer.expected +19 -0
- data/test/command/suite/groonga/delete/mismatched-type-key/acceptable/string-for-integer.test +17 -0
- data/test/command/suite/message/error/missing-dataset.test +1 -0
- data/test/command/suite/system/absorb-data/records.catalog.json +58 -0
- data/test/command/suite/system/absorb-data/records.expected +32 -0
- data/test/command/suite/system/absorb-data/records.test +24 -0
- data/test/command/suite/system/statistics/object/count/empty.expected +11 -0
- data/test/command/suite/system/statistics/object/count/empty.test +12 -0
- data/test/command/suite/system/statistics/object/count/per-volume/empty.catalog.json +36 -0
- data/test/command/suite/system/statistics/object/count/per-volume/empty.expected +19 -0
- data/test/command/suite/system/statistics/object/count/per-volume/empty.test +12 -0
- data/test/command/suite/system/statistics/object/count/per-volume/record.catalog.json +40 -0
- data/test/command/suite/system/statistics/object/count/per-volume/record.expected +19 -0
- data/test/command/suite/system/statistics/object/count/per-volume/record.test +23 -0
- data/test/command/suite/system/statistics/object/count/per-volume/schema.catalog.json +40 -0
- data/test/command/suite/system/statistics/object/count/per-volume/schema.expected +19 -0
- data/test/command/suite/system/statistics/object/count/per-volume/schema.test +13 -0
- data/test/command/suite/system/statistics/object/count/record.catalog.json +12 -0
- data/test/command/suite/system/statistics/object/count/record.expected +11 -0
- data/test/command/suite/system/statistics/object/count/record.test +23 -0
- data/test/command/suite/system/statistics/object/count/schema.catalog.json +12 -0
- data/test/command/suite/system/statistics/object/count/schema.expected +11 -0
- data/test/command/suite/system/statistics/object/count/schema.test +13 -0
- data/test/command/suite/system/status.expected +3 -2
- data/test/unit/catalog/test_dataset.rb +4 -1
- data/test/unit/{test_catalog_generator.rb → catalog/test_generator.rb} +2 -2
- data/test/unit/catalog/test_replicas_volume.rb +79 -0
- data/test/unit/catalog/test_single_volume.rb +2 -2
- data/test/unit/catalog/test_slice.rb +33 -1
- data/test/unit/catalog/{test_collection_volume.rb → test_slices_volume.rb} +72 -11
- data/test/unit/catalog/test_version2.rb +3 -0
- data/test/unit/helper/distributed_search_planner_helper.rb +2 -2
- data/test/unit/plugins/catalog/test_fetch.rb +4 -4
- data/test/unit/plugins/crud/test_add.rb +44 -4
- data/test/unit/plugins/groonga/test_column_create.rb +4 -4
- data/test/unit/plugins/groonga/test_column_list.rb +4 -4
- data/test/unit/plugins/groonga/test_column_remove.rb +4 -4
- data/test/unit/plugins/groonga/test_column_rename.rb +4 -4
- data/test/unit/plugins/groonga/test_delete.rb +73 -10
- data/test/unit/plugins/groonga/test_table_create.rb +4 -4
- data/test/unit/plugins/groonga/test_table_list.rb +4 -4
- data/test/unit/plugins/groonga/test_table_remove.rb +4 -4
- data/test/unit/plugins/search/test_handler.rb +4 -4
- data/test/unit/plugins/search/test_planner.rb +4 -2
- data/test/unit/plugins/system/test_status.rb +31 -15
- data/test/unit/plugins/test_watch.rb +16 -16
- data/test/unit/test_address.rb +4 -4
- metadata +134 -35
- data/lib/droonga/catalog/volume_collection.rb +0 -79
- data/lib/droonga/catalog_fetcher.rb +0 -53
- data/lib/droonga/catalog_generator.rb +0 -243
- data/lib/droonga/catalog_loader.rb +0 -56
- data/lib/droonga/command/remote.rb +0 -404
- data/lib/droonga/data_absorber.rb +0 -264
- data/lib/droonga/node_status.rb +0 -71
- data/lib/droonga/serf_downloader.rb +0 -115
- data/test/unit/catalog/test_volume_collection.rb +0 -78
|
@@ -0,0 +1,71 @@
|
|
|
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 "droonga/plugin"
|
|
17
|
+
require "droonga/database_scanner"
|
|
18
|
+
|
|
19
|
+
module Droonga
|
|
20
|
+
module Plugins
|
|
21
|
+
module System
|
|
22
|
+
class StatisticsObjectCountHandler < Droonga::Handler
|
|
23
|
+
include DatabaseScanner
|
|
24
|
+
|
|
25
|
+
def handle(message)
|
|
26
|
+
counts(message.request["output"])
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def counts(output)
|
|
30
|
+
counts = {}
|
|
31
|
+
if output and output.is_a?(Array)
|
|
32
|
+
if output.include?("tables")
|
|
33
|
+
counts["tables"] = n_tables
|
|
34
|
+
end
|
|
35
|
+
if output.include?("columns")
|
|
36
|
+
counts["columns"] = n_columns
|
|
37
|
+
end
|
|
38
|
+
if output.include?("records")
|
|
39
|
+
counts["records"] = n_records
|
|
40
|
+
end
|
|
41
|
+
if output.include?("total")
|
|
42
|
+
counts["total"] = total_n_objects
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
counts
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
define_single_step do |step|
|
|
50
|
+
step.name = "system.statistics.object.count"
|
|
51
|
+
step.handler = StatisticsObjectCountHandler
|
|
52
|
+
step.collector = Collectors::RecursiveSum
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
class StatisticsObjectCountPerVolumeHandler < StatisticsObjectCountHandler
|
|
56
|
+
def handle(message)
|
|
57
|
+
{
|
|
58
|
+
label => counts(message.request["output"]),
|
|
59
|
+
}
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
define_single_step do |step|
|
|
64
|
+
step.name = "system.statistics.object.count.per-volume"
|
|
65
|
+
step.use_all_replicas = true
|
|
66
|
+
step.handler = StatisticsObjectCountPerVolumeHandler
|
|
67
|
+
step.collector = Collectors::Sum
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Copyright (C) 2013-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 "droonga/plugin"
|
|
17
|
+
|
|
18
|
+
module Droonga
|
|
19
|
+
module Plugins
|
|
20
|
+
module System
|
|
21
|
+
class StatusHandler < Droonga::Handler
|
|
22
|
+
action.synchronous = true
|
|
23
|
+
|
|
24
|
+
def handle(message)
|
|
25
|
+
{
|
|
26
|
+
"nodes" => cluster.engine_nodes_status,
|
|
27
|
+
"reporter" => reporter,
|
|
28
|
+
}
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
def cluster
|
|
33
|
+
@messenger.cluster
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def reporter
|
|
37
|
+
"#{engine_state.internal_name} @ #{engine_state.name}"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def engine_state
|
|
41
|
+
@messenger.engine_state
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
define_single_step do |step|
|
|
46
|
+
step.name = "system.status"
|
|
47
|
+
step.single_operation = true
|
|
48
|
+
step.handler = StatusHandler
|
|
49
|
+
step.collector = Collectors::Or
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright (C) 2014 Droonga Project
|
|
1
|
+
# Copyright (C) 2014-2015 Droonga Project
|
|
2
2
|
#
|
|
3
3
|
# This library is free software; you can redistribute it and/or
|
|
4
4
|
# modify it under the terms of the GNU Lesser General Public
|
|
@@ -18,6 +18,8 @@ module Droonga
|
|
|
18
18
|
module Messages
|
|
19
19
|
STOP_GRACEFUL = "stop-graceful\n"
|
|
20
20
|
STOP_IMMEDIATELY = "stop-immediately\n"
|
|
21
|
+
REFRESH_SELF_REFERENCE = "refresh-self-reference\n"
|
|
22
|
+
REFRESH_NODE_REFERENCE = "refresh-node-reference\n"
|
|
21
23
|
|
|
22
24
|
READY = "ready\n"
|
|
23
25
|
FINISH = "finish\n"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright (C) 2014 Droonga Project
|
|
1
|
+
# Copyright (C) 2014-2015 Droonga Project
|
|
2
2
|
#
|
|
3
3
|
# This library is free software; you can redistribute it and/or
|
|
4
4
|
# modify it under the terms of the GNU Lesser General Public
|
|
@@ -15,52 +15,72 @@
|
|
|
15
15
|
|
|
16
16
|
require "coolio"
|
|
17
17
|
|
|
18
|
+
require "droonga/loggable"
|
|
19
|
+
require "droonga/deferrable"
|
|
18
20
|
require "droonga/process_control_protocol"
|
|
19
21
|
require "droonga/line_buffer"
|
|
20
22
|
|
|
21
23
|
module Droonga
|
|
22
24
|
class ProcessSupervisor
|
|
25
|
+
include Loggable
|
|
26
|
+
include Deferrable
|
|
23
27
|
include ProcessControlProtocol
|
|
24
28
|
|
|
29
|
+
attr_writer :on_finish
|
|
30
|
+
|
|
25
31
|
def initialize(loop, input, output)
|
|
26
32
|
@loop = loop
|
|
27
33
|
@input = create_input(input)
|
|
28
34
|
@output = create_output(output)
|
|
29
|
-
@on_ready = nil
|
|
30
|
-
@on_finish = nil
|
|
31
35
|
end
|
|
32
36
|
|
|
33
37
|
def start
|
|
34
38
|
@loop.attach(@input)
|
|
39
|
+
logger.trace("start: input watcher attached",
|
|
40
|
+
:watcher => @input)
|
|
35
41
|
@loop.attach(@output)
|
|
42
|
+
logger.trace("start: output watcher attached",
|
|
43
|
+
:watcher => @output)
|
|
36
44
|
end
|
|
37
45
|
|
|
38
46
|
def stop
|
|
39
47
|
@input.close
|
|
48
|
+
logger.trace("stop: input watcher detached",
|
|
49
|
+
:watcher => @input)
|
|
40
50
|
@output.close
|
|
51
|
+
logger.trace("stop: output watcher detached",
|
|
52
|
+
:watcher => @output)
|
|
41
53
|
end
|
|
42
54
|
|
|
43
55
|
def stop_gracefully
|
|
56
|
+
logger.trace("stop_gracefully: start")
|
|
44
57
|
@output.write(Messages::STOP_GRACEFUL)
|
|
58
|
+
logger.trace("stop_gracefully: done")
|
|
45
59
|
end
|
|
46
60
|
|
|
47
61
|
def stop_immediately
|
|
62
|
+
logger.trace("stop_immediately: start")
|
|
48
63
|
@output.write(Messages::STOP_IMMEDIATELY)
|
|
64
|
+
logger.trace("stop_immediately: done")
|
|
49
65
|
end
|
|
50
66
|
|
|
51
|
-
def
|
|
52
|
-
|
|
67
|
+
def refresh_self_reference
|
|
68
|
+
logger.trace("refresh_self_reference: start")
|
|
69
|
+
@output.write(Messages::REFRESH_SELF_REFERENCE)
|
|
70
|
+
logger.trace("refresh_self_reference: done")
|
|
53
71
|
end
|
|
54
72
|
|
|
55
|
-
def
|
|
56
|
-
|
|
73
|
+
def refresh_node_reference
|
|
74
|
+
logger.trace("refresh_node_reference: start")
|
|
75
|
+
@output.write(Messages::REFRESH_NODE_REFERENCE)
|
|
76
|
+
logger.trace("refresh_node_reference: done")
|
|
57
77
|
end
|
|
58
78
|
|
|
59
79
|
private
|
|
60
80
|
def create_input(raw_input)
|
|
61
81
|
input = Coolio::IO.new(raw_input)
|
|
62
82
|
line_buffer = LineBuffer.new
|
|
63
|
-
on_read
|
|
83
|
+
input.on_read do |data|
|
|
64
84
|
line_buffer.feed(data) do |line|
|
|
65
85
|
case line
|
|
66
86
|
when Messages::READY
|
|
@@ -70,9 +90,6 @@ module Droonga
|
|
|
70
90
|
end
|
|
71
91
|
end
|
|
72
92
|
end
|
|
73
|
-
input.on_read do |data|
|
|
74
|
-
on_read.call(data)
|
|
75
|
-
end
|
|
76
93
|
input
|
|
77
94
|
end
|
|
78
95
|
|
|
@@ -80,12 +97,12 @@ module Droonga
|
|
|
80
97
|
Coolio::IO.new(raw_output)
|
|
81
98
|
end
|
|
82
99
|
|
|
83
|
-
def on_ready
|
|
84
|
-
@on_ready.call if @on_ready
|
|
85
|
-
end
|
|
86
|
-
|
|
87
100
|
def on_finish
|
|
88
101
|
@on_finish.call if @on_finish
|
|
89
102
|
end
|
|
103
|
+
|
|
104
|
+
def log_tag
|
|
105
|
+
"process_supervisor"
|
|
106
|
+
end
|
|
90
107
|
end
|
|
91
108
|
end
|
data/lib/droonga/reducer.rb
CHANGED
|
@@ -58,6 +58,10 @@ module Droonga
|
|
|
58
58
|
reduced_value = sum(left_value, right_value)
|
|
59
59
|
reduced_value = self.class.apply_range(reduced_value,
|
|
60
60
|
"limit" => @deal["limit"])
|
|
61
|
+
when "recursive-sum"
|
|
62
|
+
reduced_value = recursive_sum(left_value, right_value)
|
|
63
|
+
reduced_value = self.class.apply_range(reduced_value,
|
|
64
|
+
"limit" => @deal["limit"])
|
|
61
65
|
when "average"
|
|
62
66
|
reduced_value = (left_value.to_f + right_value.to_f) / 2
|
|
63
67
|
when "sort"
|
|
@@ -83,6 +87,22 @@ module Droonga
|
|
|
83
87
|
end
|
|
84
88
|
end
|
|
85
89
|
|
|
90
|
+
def recursive_sum(x, y)
|
|
91
|
+
return x || y if x.nil? or y.nil?
|
|
92
|
+
|
|
93
|
+
if x.is_a?(Hash) and y.is_a?(Hash)
|
|
94
|
+
if numeric_hash?(x) and numeric_hash?(y)
|
|
95
|
+
sum_numeric_hashes(x, y)
|
|
96
|
+
else
|
|
97
|
+
x.merge(y)
|
|
98
|
+
end
|
|
99
|
+
elsif numeric_array?(x) and numeric_array?(y)
|
|
100
|
+
sum_numeric_array(x, y)
|
|
101
|
+
else
|
|
102
|
+
x + y
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
86
106
|
def merge(x, y, options={})
|
|
87
107
|
operators = options[:operators] = normalize_operators(options[:operators])
|
|
88
108
|
|
|
@@ -165,5 +185,54 @@ module Droonga
|
|
|
165
185
|
end
|
|
166
186
|
end
|
|
167
187
|
end
|
|
188
|
+
|
|
189
|
+
def numeric_array?(array)
|
|
190
|
+
return false unless array.is_a?(Array)
|
|
191
|
+
array.all? do |value|
|
|
192
|
+
case value
|
|
193
|
+
when Numeric
|
|
194
|
+
true
|
|
195
|
+
when Hash
|
|
196
|
+
numeric_hash?(value)
|
|
197
|
+
when Array
|
|
198
|
+
numeric_array?(value)
|
|
199
|
+
else
|
|
200
|
+
false
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def numeric_hash?(hash)
|
|
206
|
+
return false unless hash.is_a?(Hash)
|
|
207
|
+
numeric_array?(hash.values)
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def sum_numeric_values(x, y)
|
|
211
|
+
if x.nil? or y.nil?
|
|
212
|
+
x || y
|
|
213
|
+
elsif numeric_array?(x) and numeric_array?(y)
|
|
214
|
+
sum_numeric_arrays(x, y)
|
|
215
|
+
elsif numeric_hash?(x) and numeric_hash?(y)
|
|
216
|
+
sum_numeric_hashes(x, y)
|
|
217
|
+
else
|
|
218
|
+
x + y
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def sum_numeric_arrays(x, y)
|
|
223
|
+
sum = []
|
|
224
|
+
[x.size, y.size].max.times do |index|
|
|
225
|
+
sum << sum_numeric_values(x[index], y[index])
|
|
226
|
+
end
|
|
227
|
+
sum
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def sum_numeric_hashes(x, y)
|
|
231
|
+
sum = {}
|
|
232
|
+
(x.keys + y.keys).uniq.each do |key|
|
|
233
|
+
sum[key] = sum_numeric_values(x[key], y[key])
|
|
234
|
+
end
|
|
235
|
+
sum
|
|
236
|
+
end
|
|
168
237
|
end
|
|
169
238
|
end
|
|
@@ -24,7 +24,7 @@ module Droonga
|
|
|
24
24
|
# Don't output the file directly to prevent loading of incomplete file!
|
|
25
25
|
path = Pathname(path).expand_path
|
|
26
26
|
FileUtils.mkdir_p(path.dirname.to_s)
|
|
27
|
-
Tempfile.open(path.basename.to_s, path.dirname.to_s
|
|
27
|
+
Tempfile.open(path.basename.to_s, path.dirname.to_s) do |output|
|
|
28
28
|
if block_given?
|
|
29
29
|
yield(output, output.path)
|
|
30
30
|
else
|
data/lib/droonga/serf.rb
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright (C) 2014 Droonga Project
|
|
1
|
+
# Copyright (C) 2014-2015 Droonga Project
|
|
2
2
|
#
|
|
3
3
|
# This library is free software; you can redistribute it and/or
|
|
4
4
|
# modify it under the terms of the GNU Lesser General Public
|
|
@@ -13,161 +13,256 @@
|
|
|
13
13
|
# License along with this library; if not, write to the Free Software
|
|
14
14
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
15
15
|
|
|
16
|
-
require "English"
|
|
17
|
-
|
|
18
16
|
require "json"
|
|
19
|
-
require "coolio"
|
|
20
|
-
require "open3"
|
|
21
17
|
|
|
22
18
|
require "droonga/path"
|
|
23
19
|
require "droonga/loggable"
|
|
24
|
-
require "droonga/
|
|
25
|
-
require "droonga/
|
|
26
|
-
require "droonga/
|
|
20
|
+
require "droonga/catalog/loader"
|
|
21
|
+
require "droonga/node_name"
|
|
22
|
+
require "droonga/node_role"
|
|
23
|
+
require "droonga/serf/tag"
|
|
24
|
+
require "droonga/serf/downloader"
|
|
25
|
+
require "droonga/serf/agent"
|
|
26
|
+
require "droonga/serf/command"
|
|
27
27
|
require "droonga/line_buffer"
|
|
28
|
+
require "droonga/safe_file_writer"
|
|
29
|
+
require "droonga/service_installation"
|
|
28
30
|
|
|
29
31
|
module Droonga
|
|
30
32
|
class Serf
|
|
31
|
-
ROLE = {
|
|
32
|
-
:default => {
|
|
33
|
-
:port => 7946,
|
|
34
|
-
},
|
|
35
|
-
:source => {
|
|
36
|
-
:port => 7947,
|
|
37
|
-
},
|
|
38
|
-
:destination => {
|
|
39
|
-
:port => 7948,
|
|
40
|
-
},
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
class << self
|
|
44
|
-
def path
|
|
45
|
-
Droonga::Path.base + "serf"
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
|
|
49
33
|
include Loggable
|
|
50
34
|
|
|
51
|
-
def initialize(
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
@
|
|
55
|
-
@
|
|
56
|
-
@
|
|
35
|
+
def initialize(name, options={})
|
|
36
|
+
@serf_command = nil
|
|
37
|
+
@name = NodeName.parse(name)
|
|
38
|
+
@verbose = options[:verbose] || false
|
|
39
|
+
@service_installation = ServiceInstallation.new
|
|
40
|
+
@tags_cache = {}
|
|
57
41
|
end
|
|
58
42
|
|
|
59
|
-
def
|
|
60
|
-
logger.trace("
|
|
43
|
+
def run_agent(loop)
|
|
44
|
+
logger.trace("run_agent: start")
|
|
61
45
|
ensure_serf
|
|
62
|
-
ENV["SERF"] = @serf
|
|
63
|
-
ENV["SERF_RPC_ADDRESS"] = rpc_address
|
|
64
46
|
retry_joins = []
|
|
65
47
|
detect_other_hosts.each do |other_host|
|
|
66
48
|
retry_joins.push("-retry-join", other_host)
|
|
67
49
|
end
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
50
|
+
tags_file = Path.serf_tags_file
|
|
51
|
+
FileUtils.mkdir_p(tags_file.dirname)
|
|
52
|
+
agent = Agent.new(loop, @serf_command,
|
|
53
|
+
@name.host, agent_port, rpc_port,
|
|
54
|
+
"-node", @name.to_s,
|
|
55
|
+
"-event-handler", "droonga-engine-serf-event-handler",
|
|
56
|
+
"-tags-file", tags_file.to_s,
|
|
57
|
+
*retry_joins)
|
|
58
|
+
agent.start
|
|
59
|
+
logger.trace("run_agent: done")
|
|
60
|
+
agent
|
|
77
61
|
end
|
|
78
62
|
|
|
79
|
-
def
|
|
80
|
-
|
|
63
|
+
def initialize_tags
|
|
64
|
+
set_tag(Tag.node_type, "engine")
|
|
65
|
+
set_tag(Tag.node_role, role)
|
|
66
|
+
set_tag(Tag.cluster_id, cluster_id)
|
|
81
67
|
end
|
|
82
68
|
|
|
83
|
-
def
|
|
84
|
-
|
|
85
|
-
run("leave").stop
|
|
86
|
-
@agent.stop
|
|
87
|
-
@agent = nil
|
|
88
|
-
logger.trace("stop: done")
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
def restart
|
|
92
|
-
logger.trace("restart: start")
|
|
93
|
-
stop
|
|
94
|
-
start
|
|
95
|
-
logger.trace("restart: done")
|
|
69
|
+
def leave
|
|
70
|
+
run_command("leave")
|
|
96
71
|
end
|
|
97
72
|
|
|
98
73
|
def join(*hosts)
|
|
99
|
-
ensure_serf
|
|
100
74
|
nodes = hosts.collect do |host|
|
|
101
|
-
"#{host}:#{
|
|
75
|
+
"#{host}:#{agent_port}"
|
|
102
76
|
end
|
|
103
|
-
|
|
77
|
+
run_command("join", *nodes)
|
|
104
78
|
end
|
|
105
79
|
|
|
106
80
|
def send_query(query, payload)
|
|
107
|
-
ensure_serf
|
|
108
81
|
options = ["-format", "json"] + additional_options_from_payload(payload)
|
|
109
82
|
options += [query, JSON.generate(payload)]
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
83
|
+
raw_serf_response = run_command("query", *options)
|
|
84
|
+
serf_response = JSON.parse(raw_serf_response)
|
|
85
|
+
|
|
86
|
+
node = payload["node"]
|
|
87
|
+
if node
|
|
88
|
+
responses = serf_response["Responses"]
|
|
89
|
+
response = responses[node]
|
|
115
90
|
if response.is_a?(String)
|
|
116
91
|
begin
|
|
117
|
-
|
|
92
|
+
JSON.parse(response)
|
|
118
93
|
rescue JSON::ParserError
|
|
119
|
-
|
|
94
|
+
response
|
|
120
95
|
end
|
|
121
96
|
else
|
|
122
|
-
|
|
97
|
+
response
|
|
123
98
|
end
|
|
99
|
+
else
|
|
100
|
+
response
|
|
124
101
|
end
|
|
125
|
-
result
|
|
126
102
|
end
|
|
127
103
|
|
|
128
|
-
def
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
104
|
+
def update_cluster_state
|
|
105
|
+
path = Path.cluster_state
|
|
106
|
+
new_state = current_cluster_state
|
|
107
|
+
file_contents = JSON.pretty_generate(new_state)
|
|
108
|
+
SafeFileWriter.write(path) do |output, file|
|
|
109
|
+
output.puts(file_contents)
|
|
110
|
+
@service_installation.ensure_correct_file_permission(file)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def current_members
|
|
115
|
+
raw_response = run_command("members", "-format", "json")
|
|
116
|
+
response = JSON.parse(raw_response)
|
|
117
|
+
response["members"]
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def current_cluster_state
|
|
134
121
|
current_cluster_id = cluster_id
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
122
|
+
nodes = {}
|
|
123
|
+
unprocessed_messages_existence = {}
|
|
124
|
+
current_members.each do |member|
|
|
125
|
+
foreign = member["tags"][Tag.cluster_id] != current_cluster_id
|
|
126
|
+
next if foreign
|
|
127
|
+
|
|
128
|
+
member["tags"].each do |key, value|
|
|
129
|
+
next unless Tag.have_unprocessed_messages_tag?(key)
|
|
130
|
+
node_name = Tag.extract_node_name_from_have_unprocessed_messages_tag(key)
|
|
131
|
+
next if unprocessed_messages_existence[node_name]
|
|
132
|
+
unprocessed_messages_existence[node_name] = value == "true"
|
|
142
133
|
end
|
|
134
|
+
|
|
135
|
+
nodes[member["name"]] = {
|
|
136
|
+
"type" => member["tags"][Tag.node_type],
|
|
137
|
+
"role" => member["tags"][Tag.node_role],
|
|
138
|
+
"internal_name" => member["tags"][Tag.internal_node_name],
|
|
139
|
+
"accept_messages_newer_than" => member["tags"][Tag.accept_messages_newer_than],
|
|
140
|
+
"live" => member["status"] == "alive",
|
|
141
|
+
}
|
|
142
|
+
end
|
|
143
|
+
unprocessed_messages_existence.each do |node_name, have_messages|
|
|
144
|
+
nodes[node_name]["have_unprocessed_messages"] = have_messages
|
|
145
|
+
end
|
|
146
|
+
sorted_nodes = {}
|
|
147
|
+
nodes.keys.sort.each do |key|
|
|
148
|
+
sorted_nodes[key] = nodes[key]
|
|
149
|
+
end
|
|
150
|
+
sorted_nodes
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def get_tag(name)
|
|
154
|
+
myself = current_members.find do |member|
|
|
155
|
+
member["name"] == @name.to_s
|
|
156
|
+
end
|
|
157
|
+
if myself
|
|
158
|
+
myself["tags"][name]
|
|
159
|
+
else
|
|
160
|
+
nil
|
|
143
161
|
end
|
|
144
|
-
nodes
|
|
145
162
|
end
|
|
146
163
|
|
|
147
164
|
def set_tag(name, value)
|
|
148
|
-
|
|
149
|
-
|
|
165
|
+
run_command("tags", "-set", "#{name}=#{value}")
|
|
166
|
+
@tags_cache[name] = value
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def delete_tag(name)
|
|
170
|
+
run_command("tags", "-delete", name)
|
|
171
|
+
@tags_cache.delete(name)
|
|
150
172
|
end
|
|
151
173
|
|
|
152
174
|
def update_cluster_id
|
|
153
|
-
set_tag(
|
|
175
|
+
set_tag(Tag.cluster_id, cluster_id)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def set_have_unprocessed_messages_for(node_name)
|
|
179
|
+
tag = Tag.have_unprocessed_messages_tag_for(node_name)
|
|
180
|
+
set_tag(tag, true) unless @tags_cache.key?(tag)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def reset_have_unprocessed_messages_for(node_name)
|
|
184
|
+
delete_tag(Tag.have_unprocessed_messages_tag_for(node_name))
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def role
|
|
188
|
+
NodeRole.normalize(get_tag(Tag.node_role))
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def role=(new_role)
|
|
192
|
+
role = NodeRole.normalize(new_role)
|
|
193
|
+
set_tag(Tag.node_role, role)
|
|
194
|
+
# after that you must run update_cluster_state to update the cluster information cache
|
|
195
|
+
role
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def last_message_timestamp
|
|
199
|
+
response = send_query("report_last_message_timestamp",
|
|
200
|
+
"node" => @name.to_s)
|
|
201
|
+
if response
|
|
202
|
+
response["timestamp"]
|
|
203
|
+
else
|
|
204
|
+
nil
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def accept_messages_newer_than_timestamp
|
|
209
|
+
get_tag(Tag.accept_messages_newer_than)
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def accept_messages_newer_than(timestamp)
|
|
213
|
+
set_tag(Tag.accept_messages_newer_than, timestamp)
|
|
214
|
+
# after that you must run update_cluster_state to update the cluster information cache
|
|
154
215
|
end
|
|
155
216
|
|
|
156
217
|
def cluster_id
|
|
157
|
-
loader =
|
|
218
|
+
loader = Catalog::Loader.new(Path.catalog.to_s)
|
|
158
219
|
catalog = loader.load
|
|
159
220
|
catalog.cluster_id
|
|
160
221
|
end
|
|
161
222
|
|
|
223
|
+
CHECK_RESTARTED_INTERVAL = 3
|
|
224
|
+
CHECK_RESTARTED_TIMEOUT = 60 * 5
|
|
225
|
+
|
|
226
|
+
def ensure_restarted(*nodes, &block)
|
|
227
|
+
nodes << @name.to_s if nodes.empty?
|
|
228
|
+
|
|
229
|
+
targets = nodes.collect do |node|
|
|
230
|
+
serf = self.class.new(node)
|
|
231
|
+
{
|
|
232
|
+
:serf => serf,
|
|
233
|
+
:previous_name => serf.get_tag(Tag.internal_node_name),
|
|
234
|
+
}
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
start_time = Time.now
|
|
238
|
+
|
|
239
|
+
yield # the given operation must restart the service.
|
|
240
|
+
|
|
241
|
+
while Time.now - start_time < CHECK_RESTARTED_TIMEOUT
|
|
242
|
+
puts "Checking nodes are restarted or not:" if @verbose
|
|
243
|
+
targets.reject! do |target|
|
|
244
|
+
name = target[:serf].get_tag(Tag.internal_node_name)
|
|
245
|
+
restarted = name != target[:previous_name]
|
|
246
|
+
puts " #{name} vs #{target[:previous_name]} => " +
|
|
247
|
+
"#{restarted ? "restarted" : "not yet"}" if @verbose
|
|
248
|
+
restarted
|
|
249
|
+
end
|
|
250
|
+
break if targets.empty?
|
|
251
|
+
sleep(CHECK_RESTARTED_INTERVAL)
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
targets.empty?
|
|
255
|
+
end
|
|
256
|
+
|
|
162
257
|
private
|
|
163
258
|
def ensure_serf
|
|
164
|
-
@
|
|
165
|
-
return if @
|
|
259
|
+
@serf_command ||= find_system_serf
|
|
260
|
+
return if @serf_command
|
|
166
261
|
|
|
167
|
-
serf_path =
|
|
168
|
-
@
|
|
262
|
+
serf_path = Path.serf_command
|
|
263
|
+
@serf_command = serf_path.to_s
|
|
169
264
|
return if serf_path.executable?
|
|
170
|
-
downloader =
|
|
265
|
+
downloader = Downloader.new(serf_path)
|
|
171
266
|
downloader.download
|
|
172
267
|
end
|
|
173
268
|
|
|
@@ -180,19 +275,13 @@ module Droonga
|
|
|
180
275
|
nil
|
|
181
276
|
end
|
|
182
277
|
|
|
183
|
-
def
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
def run_once(command, *options)
|
|
192
|
-
process = SerfProcess.new(@loop, @serf, command,
|
|
193
|
-
"-rpc-addr", rpc_address,
|
|
194
|
-
*options)
|
|
195
|
-
process.run_once
|
|
278
|
+
def run_command(command, *options)
|
|
279
|
+
ensure_serf
|
|
280
|
+
command = Command.new(@serf_command, command,
|
|
281
|
+
"-rpc-addr", rpc_address,
|
|
282
|
+
*options)
|
|
283
|
+
command.verbose = @verbose
|
|
284
|
+
command.run
|
|
196
285
|
end
|
|
197
286
|
|
|
198
287
|
def additional_options_from_payload(payload)
|
|
@@ -204,192 +293,34 @@ module Droonga
|
|
|
204
293
|
end
|
|
205
294
|
|
|
206
295
|
def extract_host(node_name)
|
|
207
|
-
node_name.split(":").first
|
|
208
|
-
end
|
|
209
|
-
|
|
210
|
-
def log_level
|
|
211
|
-
level = Logger::Level.default
|
|
212
|
-
case level
|
|
213
|
-
when "trace", "debug", "info", "warn"
|
|
214
|
-
level
|
|
215
|
-
when "error", "fatal"
|
|
216
|
-
"err"
|
|
217
|
-
else
|
|
218
|
-
level # Or error?
|
|
219
|
-
end
|
|
296
|
+
node_name.to_s.split(":").first
|
|
220
297
|
end
|
|
221
298
|
|
|
222
299
|
def rpc_address
|
|
223
|
-
"#{
|
|
224
|
-
end
|
|
225
|
-
|
|
226
|
-
def node_status
|
|
227
|
-
@node_status ||= NodeStatus.new
|
|
300
|
+
"#{@name.host}:#{rpc_port}"
|
|
228
301
|
end
|
|
229
302
|
|
|
230
|
-
def
|
|
231
|
-
|
|
232
|
-
role = node_status.get(:role).to_sym
|
|
233
|
-
if self.class::ROLE.key?(role)
|
|
234
|
-
return role
|
|
235
|
-
end
|
|
236
|
-
end
|
|
237
|
-
:default
|
|
303
|
+
def rpc_port
|
|
304
|
+
7373
|
|
238
305
|
end
|
|
239
306
|
|
|
240
|
-
def
|
|
241
|
-
|
|
307
|
+
def agent_port
|
|
308
|
+
Agent::PORT
|
|
242
309
|
end
|
|
243
310
|
|
|
244
311
|
def detect_other_hosts
|
|
245
|
-
loader =
|
|
312
|
+
loader = Catalog::Loader.new(Path.catalog.to_s)
|
|
246
313
|
catalog = loader.load
|
|
247
314
|
other_nodes = catalog.all_nodes.reject do |node|
|
|
248
|
-
node == @name
|
|
315
|
+
node == @name.to_s
|
|
249
316
|
end
|
|
250
317
|
other_nodes.collect do |node|
|
|
251
|
-
|
|
318
|
+
NodeName.parse(node).host
|
|
252
319
|
end
|
|
253
320
|
end
|
|
254
321
|
|
|
255
322
|
def log_tag
|
|
256
323
|
"serf"
|
|
257
324
|
end
|
|
258
|
-
|
|
259
|
-
class SerfProcess
|
|
260
|
-
include Loggable
|
|
261
|
-
|
|
262
|
-
def initialize(loop, serf, command, *options)
|
|
263
|
-
@loop = loop
|
|
264
|
-
@serf = serf
|
|
265
|
-
@command = command
|
|
266
|
-
@options = options
|
|
267
|
-
@pid = nil
|
|
268
|
-
end
|
|
269
|
-
|
|
270
|
-
def start
|
|
271
|
-
capture_output do |output_write, error_write|
|
|
272
|
-
env = {}
|
|
273
|
-
spawn_options = {
|
|
274
|
-
:out => output_write,
|
|
275
|
-
:err => error_write,
|
|
276
|
-
}
|
|
277
|
-
@pid = spawn(env, @serf, @command, *@options, spawn_options)
|
|
278
|
-
end
|
|
279
|
-
end
|
|
280
|
-
|
|
281
|
-
def stop
|
|
282
|
-
return if @pid.nil?
|
|
283
|
-
Process.waitpid(@pid)
|
|
284
|
-
@output_io.close
|
|
285
|
-
@error_io.close
|
|
286
|
-
@pid = nil
|
|
287
|
-
end
|
|
288
|
-
|
|
289
|
-
def running?
|
|
290
|
-
not @pid.nil?
|
|
291
|
-
end
|
|
292
|
-
|
|
293
|
-
def run_once
|
|
294
|
-
stdout, stderror, status = Open3.capture3(@serf, @command, *@options, :pgroup => true)
|
|
295
|
-
{
|
|
296
|
-
:result => stdout,
|
|
297
|
-
:error => stderror,
|
|
298
|
-
:status => status,
|
|
299
|
-
}
|
|
300
|
-
end
|
|
301
|
-
|
|
302
|
-
private
|
|
303
|
-
def capture_output
|
|
304
|
-
result = nil
|
|
305
|
-
output_read, output_write = IO.pipe
|
|
306
|
-
error_read, error_write = IO.pipe
|
|
307
|
-
|
|
308
|
-
begin
|
|
309
|
-
result = yield(output_write, error_write)
|
|
310
|
-
rescue
|
|
311
|
-
output_read.close unless output_read.closed?
|
|
312
|
-
output_write.close unless output_write.closed?
|
|
313
|
-
error_read.close unless error_read.closed?
|
|
314
|
-
error_write.close unless error_write.closed?
|
|
315
|
-
raise
|
|
316
|
-
end
|
|
317
|
-
|
|
318
|
-
output_line_buffer = LineBuffer.new
|
|
319
|
-
on_read_output = lambda do |data|
|
|
320
|
-
on_standard_output(output_line_buffer, data)
|
|
321
|
-
end
|
|
322
|
-
@output_io = Coolio::IO.new(output_read)
|
|
323
|
-
@output_io.on_read do |data|
|
|
324
|
-
on_read_output.call(data)
|
|
325
|
-
end
|
|
326
|
-
# TODO: Don't allow nil for loop. It reduces nil checks and
|
|
327
|
-
# simplifies source code.
|
|
328
|
-
@loop.attach(@output_io) if @loop
|
|
329
|
-
|
|
330
|
-
error_line_buffer = LineBuffer.new
|
|
331
|
-
on_read_error = lambda do |data|
|
|
332
|
-
on_error_output(error_line_buffer, data)
|
|
333
|
-
end
|
|
334
|
-
@error_io = Coolio::IO.new(error_read)
|
|
335
|
-
@error_io.on_read do |data|
|
|
336
|
-
on_read_error.call(data)
|
|
337
|
-
end
|
|
338
|
-
# TODO: Don't allow nil for loop. It reduces nil checks and
|
|
339
|
-
# simplifies source code.
|
|
340
|
-
@loop.attach(@error_io) if @loop
|
|
341
|
-
|
|
342
|
-
result
|
|
343
|
-
end
|
|
344
|
-
|
|
345
|
-
def on_standard_output(line_buffer, data)
|
|
346
|
-
line_buffer.feed(data) do |line|
|
|
347
|
-
line = line.chomp
|
|
348
|
-
case line
|
|
349
|
-
when /\A==> /
|
|
350
|
-
content = $POSTMATCH
|
|
351
|
-
logger.info(content)
|
|
352
|
-
when /\A /
|
|
353
|
-
content = $POSTMATCH
|
|
354
|
-
case content
|
|
355
|
-
when /\A(\d{4})\/(\d{2})\/(\d{2}) (\d{2}):(\d{2}):(\d{2}) \[(\w+)\] /
|
|
356
|
-
year, month, day = $1, $2, $3
|
|
357
|
-
hour, minute, second = $4, $5, $6
|
|
358
|
-
level = $7
|
|
359
|
-
content = $POSTMATCH
|
|
360
|
-
level = normalize_level(level)
|
|
361
|
-
logger.send(level, content)
|
|
362
|
-
else
|
|
363
|
-
logger.info(content)
|
|
364
|
-
end
|
|
365
|
-
else
|
|
366
|
-
logger.info(line)
|
|
367
|
-
end
|
|
368
|
-
end
|
|
369
|
-
end
|
|
370
|
-
|
|
371
|
-
def normalize_level(level)
|
|
372
|
-
level = level.downcase
|
|
373
|
-
case level
|
|
374
|
-
when "err"
|
|
375
|
-
"error"
|
|
376
|
-
else
|
|
377
|
-
level
|
|
378
|
-
end
|
|
379
|
-
end
|
|
380
|
-
|
|
381
|
-
def on_error_output(line_buffer, data)
|
|
382
|
-
line_buffer.feed(data) do |line|
|
|
383
|
-
line = line.chomp
|
|
384
|
-
logger.error(line.gsub(/\A==> /, ""))
|
|
385
|
-
end
|
|
386
|
-
end
|
|
387
|
-
|
|
388
|
-
def log_tag
|
|
389
|
-
tag = "serf"
|
|
390
|
-
tag << "[#{@pid}]" if @pid
|
|
391
|
-
tag
|
|
392
|
-
end
|
|
393
|
-
end
|
|
394
325
|
end
|
|
395
326
|
end
|