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
|
@@ -21,17 +21,17 @@ require "json"
|
|
|
21
21
|
require "pathname"
|
|
22
22
|
|
|
23
23
|
require "droonga/engine/version"
|
|
24
|
-
require "droonga/
|
|
24
|
+
require "droonga/catalog/generator"
|
|
25
25
|
require "droonga/safe_file_writer"
|
|
26
26
|
require "droonga/service_installation"
|
|
27
27
|
|
|
28
28
|
service_installation = Droonga::ServiceInstallation.new
|
|
29
29
|
service_installation.ensure_using_service_base_directory
|
|
30
30
|
|
|
31
|
-
generator = Droonga::
|
|
31
|
+
generator = Droonga::Catalog::Generator.new
|
|
32
32
|
current_dataset = {}
|
|
33
33
|
datasets = {
|
|
34
|
-
Droonga::
|
|
34
|
+
Droonga::Catalog::Generator::DEFAULT_DATASET => current_dataset
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
options = OpenStruct.new
|
|
@@ -51,37 +51,37 @@ end
|
|
|
51
51
|
parser.on("--dataset=NAME",
|
|
52
52
|
"Add a dataset its name is NAME.",
|
|
53
53
|
"And set the NAME to the current dataset.",
|
|
54
|
-
"(#{Droonga::
|
|
54
|
+
"(#{Droonga::Catalog::Generator::DEFAULT_DATASET})") do |name|
|
|
55
55
|
current_dataset = datasets[name] = {}
|
|
56
56
|
end
|
|
57
57
|
parser.on("--n-workers=N", Integer,
|
|
58
58
|
"Use N workers for the current dataset.",
|
|
59
|
-
"(#{Droonga::
|
|
59
|
+
"(#{Droonga::Catalog::Generator::DEFAULT_N_WORKERS})") do |n|
|
|
60
60
|
current_dataset[:n_workers] = n
|
|
61
61
|
end
|
|
62
62
|
parser.on("--hosts=NAME1,NAME2,...", Array,
|
|
63
63
|
"Use given hosts for replicas of the current dataset.",
|
|
64
|
-
"(#{Droonga::
|
|
64
|
+
"(#{Droonga::Catalog::Generator::DEFAULT_HOSTS.join(",")})") do |hosts|
|
|
65
65
|
current_dataset[:hosts] = hosts
|
|
66
66
|
end
|
|
67
67
|
parser.on("--port=PORT", Integer,
|
|
68
68
|
"Use the PORT as the port for the current dataset.",
|
|
69
|
-
"(#{Droonga::
|
|
69
|
+
"(#{Droonga::Catalog::Generator::DEFAULT_PORT})") do |port|
|
|
70
70
|
current_dataset[:port] = port
|
|
71
71
|
end
|
|
72
72
|
parser.on("--tag=TAG",
|
|
73
73
|
"Use the TAG as the tag for the current dataset.",
|
|
74
|
-
"(#{Droonga::
|
|
74
|
+
"(#{Droonga::Catalog::Generator::DEFAULT_TAG})") do |tag|
|
|
75
75
|
current_dataset[:tag] = tag
|
|
76
76
|
end
|
|
77
77
|
parser.on("--n-slices=N", Integer,
|
|
78
78
|
"Use N slices for each replica.",
|
|
79
|
-
"(#{Droonga::
|
|
79
|
+
"(#{Droonga::Catalog::Generator::DEFAULT_N_SLICES})") do |n|
|
|
80
80
|
current_dataset[:n_slices] = n
|
|
81
81
|
end
|
|
82
82
|
parser.on("--plugins=PLUGIN1,PLUGIN2,...", Array,
|
|
83
83
|
"Use PLUGINS for the current dataset.",
|
|
84
|
-
"(#{Droonga::
|
|
84
|
+
"(#{Droonga::Catalog::Generator::DEFAULT_PLUGINS.join(",")})") do |plugins|
|
|
85
85
|
current_dataset[:plugins] = plugins
|
|
86
86
|
end
|
|
87
87
|
parser.on("--schema=PATH",
|
|
@@ -102,8 +102,8 @@ parser.on("--replicas=PATH",
|
|
|
102
102
|
end
|
|
103
103
|
parser.parse!(ARGV)
|
|
104
104
|
|
|
105
|
-
if datasets[Droonga::
|
|
106
|
-
datasets.delete(Droonga::
|
|
105
|
+
if datasets[Droonga::Catalog::Generator::DEFAULT_DATASET].empty?
|
|
106
|
+
datasets.delete(Droonga::Catalog::Generator::DEFAULT_DATASET)
|
|
107
107
|
end
|
|
108
108
|
|
|
109
109
|
if service_installation.user_exist? and
|
|
@@ -21,17 +21,17 @@ require "json"
|
|
|
21
21
|
require "pathname"
|
|
22
22
|
|
|
23
23
|
require "droonga/engine/version"
|
|
24
|
-
require "droonga/
|
|
24
|
+
require "droonga/catalog/generator"
|
|
25
25
|
require "droonga/safe_file_writer"
|
|
26
26
|
require "droonga/service_installation"
|
|
27
27
|
|
|
28
28
|
service_installation = Droonga::ServiceInstallation.new
|
|
29
29
|
service_installation.ensure_using_service_base_directory
|
|
30
30
|
|
|
31
|
-
generator = Droonga::
|
|
31
|
+
generator = Droonga::Catalog::Generator.new
|
|
32
32
|
current_dataset = {}
|
|
33
33
|
datasets = {
|
|
34
|
-
Droonga::
|
|
34
|
+
Droonga::Catalog::Generator::DEFAULT_DATASET => current_dataset
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
options = OpenStruct.new
|
|
@@ -65,7 +65,7 @@ end
|
|
|
65
65
|
parser.on("--dataset=NAME",
|
|
66
66
|
"Add a dataset its name is NAME.",
|
|
67
67
|
"And set the NAME to the current dataset.",
|
|
68
|
-
"(#{Droonga::
|
|
68
|
+
"(#{Droonga::Catalog::Generator::DEFAULT_DATASET})") do |name|
|
|
69
69
|
current_dataset = datasets[name] = {}
|
|
70
70
|
end
|
|
71
71
|
parser.on("--replica-hosts=NAME1,NAME2,...", Array,
|
data/bin/droonga-engine-join
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
2
|
#
|
|
3
|
-
# Copyright (C) 2014 Droonga Project
|
|
3
|
+
# Copyright (C) 2014-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
|
|
@@ -19,207 +19,388 @@ require "slop"
|
|
|
19
19
|
require "json"
|
|
20
20
|
require "pathname"
|
|
21
21
|
require "socket"
|
|
22
|
+
require "coolio"
|
|
22
23
|
|
|
23
24
|
require "droonga/engine/version"
|
|
24
25
|
require "droonga/path"
|
|
25
|
-
require "droonga/
|
|
26
|
+
require "droonga/node_name"
|
|
27
|
+
require "droonga/node_role"
|
|
28
|
+
require "droonga/catalog/dataset"
|
|
29
|
+
require "droonga/catalog/fetcher"
|
|
30
|
+
require "droonga/catalog/loader"
|
|
26
31
|
require "droonga/safe_file_writer"
|
|
27
|
-
require "droonga/
|
|
32
|
+
require "droonga/data_absorber_client"
|
|
28
33
|
require "droonga/serf"
|
|
29
34
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
puts "Start to join a new node #{@options[:host]}"
|
|
36
|
-
puts " to the cluster of #{@options["replica-source-host"]}"
|
|
37
|
-
puts " via #{@options["receiver-host"]} (this host)"
|
|
38
|
-
puts ""
|
|
39
|
-
|
|
40
|
-
set_node_role
|
|
41
|
-
do_join
|
|
42
|
-
sleep(5) #TODO: wait for restarting of the joining node. this should be done more safely.
|
|
43
|
-
do_copy unless @options["no-copy"]
|
|
44
|
-
set_effective_message_timestamp
|
|
45
|
-
update_other_nodes
|
|
46
|
-
reset_node_role
|
|
47
|
-
puts("Done.")
|
|
48
|
-
exit(true)
|
|
49
|
-
end
|
|
35
|
+
module Droonga
|
|
36
|
+
module Command
|
|
37
|
+
class Join
|
|
38
|
+
class MissingRequiredParameter < StandardError
|
|
39
|
+
end
|
|
50
40
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
options = Slop.parse(:help => true) do |option|
|
|
54
|
-
option.on("no-copy", "Don't copy data from the source cluster.",
|
|
55
|
-
:default => false)
|
|
56
|
-
|
|
57
|
-
option.separator("Connections:")
|
|
58
|
-
option.on(:host=,
|
|
59
|
-
"Host name of the new node to be joined.",
|
|
60
|
-
:required => true)
|
|
61
|
-
option.on("replica-source-host=",
|
|
62
|
-
"Host name of the soruce node in the cluster to be connected.",
|
|
63
|
-
:required => true)
|
|
64
|
-
option.on("receiver-host=",
|
|
65
|
-
"Host name of this host.",
|
|
66
|
-
:default => Socket.gethostname)
|
|
67
|
-
option.on(:dataset=,
|
|
68
|
-
"Dataset name of for the node to be joined.",
|
|
69
|
-
:default => Droonga::CatalogGenerator::DEFAULT_DATASET)
|
|
70
|
-
option.on(:port=,
|
|
71
|
-
"Port number of the source cluster to be connected.",
|
|
72
|
-
:as => Integer,
|
|
73
|
-
:default => Droonga::CatalogGenerator::DEFAULT_PORT)
|
|
74
|
-
option.on(:tag=,
|
|
75
|
-
"Tag name of the soruce cluster to be connected.",
|
|
76
|
-
:default => Droonga::CatalogGenerator::DEFAULT_TAG)
|
|
77
|
-
option.on("records-per-second=",
|
|
78
|
-
"Maximum number of records per second to be copied. " +
|
|
79
|
-
"'#{Droonga::Client::RateLimiter::NO_LIMIT}' means no limit.",
|
|
80
|
-
:as => Integer,
|
|
81
|
-
:default => Droonga::DataAbsorber::DEFAULT_MESSAGES_PER_SECOND)
|
|
82
|
-
end
|
|
83
|
-
@options = options
|
|
84
|
-
rescue Slop::MissingOptionError => error
|
|
85
|
-
$stderr.puts(error)
|
|
86
|
-
exit(false)
|
|
87
|
-
end
|
|
41
|
+
def run
|
|
42
|
+
@loop = Coolio::Loop.default
|
|
88
43
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
end
|
|
44
|
+
parse_options
|
|
45
|
+
trap_signals
|
|
92
46
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
47
|
+
puts "Start to join a new node #{joining_node.host}"
|
|
48
|
+
puts " to the cluster of #{source_node.host}"
|
|
49
|
+
puts " via #{@options["receiver-host"]} (this host)"
|
|
50
|
+
puts " port = #{joining_node.port}"
|
|
51
|
+
puts " tag = #{joining_node.tag}"
|
|
52
|
+
puts " dataset = #{dataset}"
|
|
53
|
+
puts ""
|
|
96
54
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
result[:response]
|
|
103
|
-
end
|
|
55
|
+
if should_copy? and not absorber.empty_destination?
|
|
56
|
+
$stderr.puts("Error: The joining node's dataset #{dataset} is not empty.")
|
|
57
|
+
$stderr.puts(" You must clear all data of the node before joining.")
|
|
58
|
+
return false
|
|
59
|
+
end
|
|
104
60
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
end
|
|
61
|
+
puts "Source Cluster ID: #{source_cluster_id}"
|
|
62
|
+
puts ""
|
|
108
63
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
64
|
+
begin
|
|
65
|
+
set_joining_node_role
|
|
66
|
+
do_join
|
|
67
|
+
register_to_existing_nodes
|
|
68
|
+
set_source_node_role
|
|
69
|
+
if should_copy?
|
|
70
|
+
#XXX If any command is received by the source node after changing of its role,
|
|
71
|
+
# the timestamp of last processed mesasge is unexpectedly updated by them.
|
|
72
|
+
# Be careful to not send any command to the source node on this timing!!
|
|
73
|
+
update_accept_messages_newer_than_timestamp
|
|
74
|
+
successed = copy_data
|
|
75
|
+
unless successed
|
|
76
|
+
do_cancel
|
|
77
|
+
return false
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
reset_source_node_role
|
|
81
|
+
reset_joining_node_role
|
|
82
|
+
puts("Done.")
|
|
83
|
+
true
|
|
84
|
+
rescue Exception => exception
|
|
85
|
+
puts("Unexpected exception: #{exception.message}")
|
|
86
|
+
puts(exception.backtrace.join("\n"))
|
|
87
|
+
do_cancel
|
|
88
|
+
false
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
private
|
|
93
|
+
def parse_options
|
|
94
|
+
options = Slop.parse(:help => true) do |option|
|
|
95
|
+
option.on("no-copy", "Don't copy data from the source cluster.",
|
|
96
|
+
:default => false)
|
|
121
97
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
98
|
+
option.separator("Target:")
|
|
99
|
+
option.on(:host=,
|
|
100
|
+
"Host name of the new node to be joined.",
|
|
101
|
+
:required => true)
|
|
102
|
+
option.on("replica-source-host=",
|
|
103
|
+
"Host name of the soruce node in the cluster to be connected.",
|
|
104
|
+
:required => true)
|
|
105
|
+
|
|
106
|
+
option.on(:port=,
|
|
107
|
+
"Port number of the source cluster to be connected.",
|
|
108
|
+
:as => Integer,
|
|
109
|
+
:default => NodeName::DEFAULT_PORT)
|
|
110
|
+
option.on(:tag=,
|
|
111
|
+
"Tag name of the soruce cluster to be connected.",
|
|
112
|
+
:default => NodeName::DEFAULT_TAG)
|
|
113
|
+
option.on(:dataset=,
|
|
114
|
+
"Dataset name of for the node to be joined.",
|
|
115
|
+
:default => Catalog::Dataset::DEFAULT_NAME)
|
|
116
|
+
|
|
117
|
+
option.separator("Connections:")
|
|
118
|
+
option.on("receiver-host=",
|
|
119
|
+
"Host name of this host.",
|
|
120
|
+
:default => Socket.gethostname)
|
|
121
|
+
|
|
122
|
+
option.separator("Miscellaneous:")
|
|
123
|
+
option.on("records-per-second=",
|
|
124
|
+
"Maximum number of records per second to be copied. " +
|
|
125
|
+
"'#{Client::RateLimiter::NO_LIMIT}' means no limit.",
|
|
126
|
+
:as => Integer,
|
|
127
|
+
:default => DataAbsorberClient::DEFAULT_MESSAGES_PER_SECOND)
|
|
128
|
+
option.on("progress-interval-seconds=",
|
|
129
|
+
"Interval seconds to report progress.",
|
|
130
|
+
:as => Integer,
|
|
131
|
+
:default => DataAbsorberClient::DEFAULT_PROGRESS_INTERVAL_SECONDS)
|
|
132
|
+
option.on(:verbose, "Output details for internal operations.",
|
|
133
|
+
:default => false)
|
|
134
|
+
end
|
|
135
|
+
@options = options
|
|
136
|
+
rescue Slop::MissingOptionError => error
|
|
137
|
+
$stderr.puts(error)
|
|
138
|
+
raise MissingRequiredParameter.new
|
|
127
139
|
end
|
|
128
|
-
run_remote_command(joining_node, "change_role",
|
|
129
|
-
"node" => joining_node,
|
|
130
|
-
"role" => "destination")
|
|
131
|
-
@node_role_changed = true
|
|
132
|
-
end
|
|
133
140
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
run_remote_command(source_node, "change_role",
|
|
137
|
-
"node" => source_node,
|
|
138
|
-
"role" => "")
|
|
141
|
+
def dataset
|
|
142
|
+
@options[:dataset]
|
|
139
143
|
end
|
|
140
|
-
run_remote_command(joining_node, "change_role",
|
|
141
|
-
"node" => joining_node,
|
|
142
|
-
"role" => "")
|
|
143
|
-
@node_role_changed = false
|
|
144
|
-
end
|
|
145
144
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
145
|
+
def should_copy?
|
|
146
|
+
not @options["no-copy"]
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def joining_node
|
|
150
|
+
@joining_node ||= NodeName.new(:host => @options[:host],
|
|
151
|
+
:port => @options[:port],
|
|
152
|
+
:tag => @options[:tag])
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def source_node
|
|
156
|
+
@source_node ||= NodeName.new(:host => @options["replica-source-host"],
|
|
157
|
+
:port => @options[:port],
|
|
158
|
+
:tag => @options[:tag])
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def source_node_serf
|
|
162
|
+
@source_node_serf ||= Serf.new(source_node.to_s,
|
|
163
|
+
:verbose => @options[:verbose])
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def joining_node_serf
|
|
167
|
+
@joining_node_serf ||= Serf.new(joining_node.to_s,
|
|
168
|
+
:verbose => @options[:verbose])
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def source_cluster_id
|
|
172
|
+
source_catalog.cluster_id
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def all_nodes
|
|
176
|
+
existing_nodes + [joining_node]
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def existing_nodes
|
|
180
|
+
@existing_nodes ||= prepare_existing_nodes
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def prepare_existing_nodes
|
|
184
|
+
generator = Catalog::Generator.new
|
|
185
|
+
generator.load(raw_source_catalog)
|
|
186
|
+
|
|
187
|
+
dataset = generator.dataset_for_host(source_node.host)
|
|
188
|
+
other_hosts = dataset.replicas.hosts
|
|
189
|
+
other_hosts.collect do |host|
|
|
190
|
+
NodeName.new(:host => host,
|
|
191
|
+
:port => source_node.port,
|
|
192
|
+
:tag => source_node.tag)
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def source_catalog
|
|
197
|
+
@source_catalog ||= parse_source_catalog
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def parse_source_catalog
|
|
201
|
+
loader = Catalog::Loader.new
|
|
202
|
+
loader.parse(raw_source_catalog)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def raw_source_catalog
|
|
206
|
+
@raw_source_catalog ||= fetch_source_catalog
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def fetch_source_catalog
|
|
210
|
+
fetcher = Catalog::Fetcher.new(:host => source_node.host,
|
|
211
|
+
:port => source_node.port,
|
|
212
|
+
:tag => source_node.tag,
|
|
213
|
+
:receiver_host => @options["receiver-host"])
|
|
214
|
+
fetcher.fetch(:dataset => dataset)
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def absorber
|
|
218
|
+
@absorber ||= prepare_absorber
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def prepare_absorber
|
|
222
|
+
absorber_options = {
|
|
223
|
+
:host => joining_node.host,
|
|
224
|
+
:port => joining_node.port,
|
|
225
|
+
:tag => joining_node.tag,
|
|
226
|
+
:dataset => dataset,
|
|
227
|
+
|
|
228
|
+
:source_host => source_node.host,
|
|
229
|
+
:source_port => source_node.port,
|
|
230
|
+
:source_tag => source_node.tag,
|
|
231
|
+
:source_dataset => dataset,
|
|
232
|
+
|
|
233
|
+
:receiver_host => @options["receiver-host"],
|
|
234
|
+
|
|
235
|
+
:messages_per_second => @options["records-per-second"],
|
|
236
|
+
:progress_interval_seconds => @options["progress-interval-seconds"],
|
|
237
|
+
:target_role => NodeRole::ABSORB_DESTINATION,
|
|
238
|
+
|
|
239
|
+
:client_options => {
|
|
240
|
+
:backend => :coolio,
|
|
241
|
+
:loop => @loop,
|
|
242
|
+
},
|
|
243
|
+
}
|
|
244
|
+
DataAbsorberClient.new(absorber_options)
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def set_source_node_role
|
|
248
|
+
return if source_node_serf.role == NodeRole::ABSORB_SOURCE
|
|
249
|
+
if absorber.source_node_suspendable?
|
|
250
|
+
puts("Changing role of the source node...")
|
|
251
|
+
source_node_serf.ensure_restarted do
|
|
252
|
+
source_node_serf.send_query("change_role",
|
|
253
|
+
"node" => source_node.to_s,
|
|
254
|
+
"role" => NodeRole::ABSORB_SOURCE)
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
end
|
|
155
258
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
259
|
+
def set_joining_node_role
|
|
260
|
+
return if joining_node_serf.role == NodeRole::ABSORB_DESTINATION
|
|
261
|
+
puts("Changing role of the joining node...")
|
|
262
|
+
joining_node_serf.ensure_restarted do
|
|
263
|
+
joining_node_serf.send_query("change_role",
|
|
264
|
+
"node" => joining_node.to_s,
|
|
265
|
+
"role" => NodeRole::ABSORB_DESTINATION)
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def reset_source_node_role
|
|
270
|
+
return if source_node_serf.role == NodeRole::SERVICE_PROVIDER
|
|
271
|
+
if absorber.source_node_suspendable?
|
|
272
|
+
puts("Restoring role of the source node...")
|
|
273
|
+
source_node_serf.ensure_restarted do
|
|
274
|
+
source_node_serf.send_query("change_role",
|
|
275
|
+
"node" => source_node.to_s,
|
|
276
|
+
"role" => NodeRole::SERVICE_PROVIDER)
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
def reset_joining_node_role
|
|
282
|
+
return if joining_node_serf.role == NodeRole::SERVICE_PROVIDER
|
|
283
|
+
puts("Restoring role of the joining node...")
|
|
284
|
+
joining_node_serf.ensure_restarted do
|
|
285
|
+
joining_node_serf.send_query("change_role",
|
|
286
|
+
"node" => joining_node.to_s,
|
|
287
|
+
"role" => NodeRole::SERVICE_PROVIDER)
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def do_join
|
|
292
|
+
puts("Configuring the joining node as a new replica for the cluster...")
|
|
293
|
+
joining_node_serf.ensure_restarted do
|
|
294
|
+
joining_node_serf.send_query("join",
|
|
295
|
+
"node" => joining_node.to_s,
|
|
296
|
+
"type" => "replica",
|
|
297
|
+
"source" => source_node.to_s,
|
|
298
|
+
"dataset" => dataset)
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
def copy_data
|
|
303
|
+
puts("Copying data from the source node...")
|
|
304
|
+
|
|
305
|
+
last_progress = nil
|
|
306
|
+
absorber.run do |progress|
|
|
307
|
+
if last_progress
|
|
308
|
+
printf("%s", "#{" " * last_progress[:message].size}\r")
|
|
309
|
+
end
|
|
310
|
+
printf("%s", "#{progress[:message]}\r")
|
|
174
311
|
last_progress = progress
|
|
175
312
|
end
|
|
313
|
+
@loop.run
|
|
314
|
+
|
|
315
|
+
if absorber.error_message
|
|
316
|
+
puts(absorber.error_message)
|
|
317
|
+
return false
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
puts ""
|
|
321
|
+
true
|
|
176
322
|
end
|
|
177
|
-
puts ""
|
|
178
|
-
end
|
|
179
323
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
324
|
+
GETTING_LAST_MESSAGE_TIMESTAMP_MAX_RETRY_COUNT = 10
|
|
325
|
+
GETTING_LAST_MESSAGE_TIMESTAMP_RETRY_INTERVAL_SECONDS = 10
|
|
326
|
+
|
|
327
|
+
def try_get_last_message_timestamp(retry_count=0)
|
|
328
|
+
puts "Getting the timestamp of the last processed message in the source node..."
|
|
329
|
+
timestamp = source_node_serf.last_message_timestamp
|
|
330
|
+
unless timestamp
|
|
331
|
+
if retry_count < GETTING_LAST_MESSAGE_TIMESTAMP_MAX_RETRY_COUNT
|
|
332
|
+
puts "Failed. Retrying..."
|
|
333
|
+
sleep(GETTING_LAST_MESSAGE_TIMESTAMP_RETRY_INTERVAL_SECONDS)
|
|
334
|
+
timestamp = try_get_last_message_timestamp(retry_count + 1)
|
|
335
|
+
end
|
|
336
|
+
end
|
|
337
|
+
timestamp
|
|
192
338
|
end
|
|
193
|
-
end
|
|
194
339
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
340
|
+
def update_accept_messages_newer_than_timestamp
|
|
341
|
+
timestamp = try_get_last_message_timestamp
|
|
342
|
+
if timestamp and not timestamp.empty?
|
|
343
|
+
puts "The timestamp of the last processed message at the source node: #{timestamp}"
|
|
344
|
+
puts "Setting new node to ignore messages older than the timestamp..."
|
|
345
|
+
joining_node_serf.ensure_restarted do
|
|
346
|
+
joining_node_serf.send_query("accept_messages_newer_than",
|
|
347
|
+
"node" => joining_node.to_s,
|
|
348
|
+
"timestamp" => timestamp)
|
|
349
|
+
end
|
|
350
|
+
else
|
|
351
|
+
$stderr.puts("WARNING: Couldn't get the time stamp of " +
|
|
352
|
+
"the last processed message from the source node. " +
|
|
353
|
+
"Any message will be forwarded to the joining node.")
|
|
354
|
+
end
|
|
355
|
+
end
|
|
201
356
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
357
|
+
def register_to_existing_nodes
|
|
358
|
+
puts("Registering new node to existing nodes...")
|
|
359
|
+
source_node_serf.ensure_restarted(*existing_nodes) do
|
|
360
|
+
source_node_serf.send_query("add_replicas",
|
|
361
|
+
"cluster_id" => source_cluster_id,
|
|
362
|
+
"dataset" => dataset,
|
|
363
|
+
"hosts" => [joining_node.host])
|
|
364
|
+
end
|
|
365
|
+
@node_registered = true
|
|
206
366
|
end
|
|
207
367
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
368
|
+
def unregister_from_existing_nodes
|
|
369
|
+
puts("Unregistering new node from existing nodes...")
|
|
370
|
+
source_node_serf.ensure_restarted(*existing_nodes) do
|
|
371
|
+
source_node_serf.send_query("remove_replicas",
|
|
372
|
+
"cluster_id" => source_cluster_id,
|
|
373
|
+
"dataset" => dataset,
|
|
374
|
+
"hosts" => [joining_node.host])
|
|
375
|
+
end
|
|
376
|
+
@node_registered = false
|
|
211
377
|
end
|
|
212
378
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
379
|
+
def trap_signals
|
|
380
|
+
trap(:TERM) do
|
|
381
|
+
trap(:TERM, "DEFAULT")
|
|
382
|
+
do_cancel
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
trap(:INT) do
|
|
386
|
+
trap(:INT, "DEFAULT")
|
|
387
|
+
do_cancel
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
trap(:QUIT) do
|
|
391
|
+
trap(:QUIT, "DEFAULT")
|
|
392
|
+
do_cancel
|
|
393
|
+
end
|
|
216
394
|
end
|
|
217
|
-
end
|
|
218
395
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
396
|
+
def do_cancel
|
|
397
|
+
#XXX we have to write more codes to cancel remote processes!
|
|
398
|
+
unregister_from_existing_nodes if @node_registered
|
|
399
|
+
reset_joining_node_role
|
|
400
|
+
reset_source_node_role
|
|
401
|
+
end
|
|
402
|
+
end
|
|
222
403
|
end
|
|
223
404
|
end
|
|
224
405
|
|
|
225
|
-
|
|
406
|
+
exit(Droonga::Command::Join.new.run)
|