cassandra-utils 0.3.2 → 0.4.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/bin/cass-util +3 -0
- data/lib/cassandra/tasks.rb +1 -0
- data/lib/cassandra/tasks/autoclean.rb +52 -10
- data/lib/cassandra/tasks/seedregistry.rb +173 -0
- data/lib/cassandra/utils/daemon.rb +6 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9e37957c030dfb9729a4d2b98df7db3acc002f68
|
4
|
+
data.tar.gz: b56baf5d6d2a88963a0803e1f0abfd1931ea7f77
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ce642b41f5a82d32cf9005a7fa263dd00be640c747b2dd70266d9876ce476845db347301dbe52a74707bafddd1b1d42186cdaa4301a1370578e2b9be8f403b65
|
7
|
+
data.tar.gz: ec1f02d0538917e2a6b64ffbeaeddce83c226b68e27215b21bfdd8602706208c71a1a1f573a7030372c65861e6854fa0ddac493007d4e2656166760e0c4106cd
|
data/bin/cass-util
CHANGED
@@ -14,6 +14,9 @@ class CassandraUtils < Thor
|
|
14
14
|
class_option :cleanup_lock_count, type: :numeric,
|
15
15
|
required: true, default: 1,
|
16
16
|
desc: 'Number of nodes that can obtain a Semaphore lock'
|
17
|
+
class_option :seed_service_name, type: :string,
|
18
|
+
required: true, default: 'cassandra',
|
19
|
+
desc: 'Unique string to be used in obtaining a Consul seed lock. Example: cassandra-seed-#{cluster_name}'
|
17
20
|
desc 'util', 'Perform various utilities'
|
18
21
|
def util
|
19
22
|
s = ::Cassandra::Utils::Daemon.new(options)
|
data/lib/cassandra/tasks.rb
CHANGED
@@ -74,12 +74,24 @@ module Cassandra
|
|
74
74
|
# Run the Cassandra cleanup process if necessary
|
75
75
|
#
|
76
76
|
def run!
|
77
|
-
|
78
|
-
|
77
|
+
node_status = status
|
78
|
+
unless node_status == :up
|
79
|
+
logger.debug "Cleanup skipped because of node status. Expected up got #{node_status}"
|
80
|
+
return
|
81
|
+
end
|
82
|
+
|
83
|
+
node_state = state
|
84
|
+
unless node_state == :normal
|
85
|
+
logger.debug "Cleanup skipped because of node state. Expected normal got #{node_state}"
|
86
|
+
return
|
87
|
+
end
|
79
88
|
|
80
89
|
new_tokens = Set.new tokens
|
81
90
|
old_tokens = Set.new cached_tokens
|
82
|
-
|
91
|
+
if new_tokens == old_tokens
|
92
|
+
logger.debug "Cleanup skipped because tokens haven't changed"
|
93
|
+
return
|
94
|
+
end
|
83
95
|
|
84
96
|
::DaemonRunner::Semaphore.lock(@service_name, @lock_count) do
|
85
97
|
result = nodetool_cleanup
|
@@ -92,18 +104,37 @@ module Cassandra
|
|
92
104
|
# @return [Array<String>] Cached tokens
|
93
105
|
#
|
94
106
|
def cached_tokens
|
107
|
+
if token_cache.closed?
|
108
|
+
logger.debug "Failed to read cached tokens because file is closed."
|
109
|
+
return []
|
110
|
+
end
|
111
|
+
|
112
|
+
token_cache.seek 0
|
95
113
|
data = token_cache.read
|
96
114
|
data = JSON.parse data
|
97
|
-
|
115
|
+
|
116
|
+
unless data['version'] == ::Cassandra::Utils::VERSION
|
117
|
+
logger.debug "Failed to read cached tokens because version didn't match. Expected #{::Cassandra::Utils::VERSION} got #{data['version']}"
|
118
|
+
return []
|
119
|
+
end
|
98
120
|
|
99
121
|
tokens = data['tokens']
|
100
|
-
|
101
|
-
|
122
|
+
if tokens.nil?
|
123
|
+
logger.debug "Failed to read cached tokens because they're nil"
|
124
|
+
return []
|
125
|
+
end
|
126
|
+
|
127
|
+
unless tokens.respond_to? :each
|
128
|
+
logger.debug "Failed to read cached tokens because they're invalid"
|
129
|
+
return []
|
130
|
+
end
|
102
131
|
|
103
132
|
tokens.sort!
|
104
133
|
tokens
|
105
134
|
# Token file could not be opend or parsed
|
106
|
-
rescue Errno::ENOENT, JSON::ParserError
|
135
|
+
rescue Errno::ENOENT, JSON::ParserError => e
|
136
|
+
logger.debug "Caught exception while reading cached tokens"
|
137
|
+
logger.debug e
|
107
138
|
[]
|
108
139
|
end
|
109
140
|
|
@@ -117,8 +148,14 @@ module Cassandra
|
|
117
148
|
:version => ::Cassandra::Utils::VERSION
|
118
149
|
}
|
119
150
|
|
151
|
+
if token_cache.closed?
|
152
|
+
logger.debug "Failed to save cached tokens because file is closed."
|
153
|
+
return []
|
154
|
+
end
|
155
|
+
|
156
|
+
token_cache.seek 0
|
157
|
+
token_cache.truncate 0
|
120
158
|
token_cache.write data.to_json
|
121
|
-
token_cache.flush
|
122
159
|
end
|
123
160
|
|
124
161
|
# Get the tokens this node owns
|
@@ -131,7 +168,11 @@ module Cassandra
|
|
131
168
|
# @return [Array<String>] Tokens owned by this node
|
132
169
|
#
|
133
170
|
def tokens
|
134
|
-
|
171
|
+
if address.nil?
|
172
|
+
logger.debug "Failed to read live tokens because address is nil"
|
173
|
+
return []
|
174
|
+
end
|
175
|
+
|
135
176
|
results = (nodetool_ring || '').split("\n")
|
136
177
|
results.map! { |line| line.strip }
|
137
178
|
results.select! { |line| line.start_with? address }
|
@@ -251,7 +292,8 @@ module Cassandra
|
|
251
292
|
# @return [File] File where tokens wil be saved
|
252
293
|
#
|
253
294
|
def token_cache
|
254
|
-
File
|
295
|
+
mode = File::CREAT | File::RDWR | File::SYNC
|
296
|
+
@token_cache ||= File.new(token_cache_path, mode)
|
255
297
|
end
|
256
298
|
end
|
257
299
|
end
|
@@ -0,0 +1,173 @@
|
|
1
|
+
require 'daemon_runner/shell_out'
|
2
|
+
require 'daemon_runner/semaphore'
|
3
|
+
|
4
|
+
module Cassandra
|
5
|
+
module Tasks
|
6
|
+
class SeedRegistry
|
7
|
+
# Create a new SeedRegistry task
|
8
|
+
#
|
9
|
+
# @param cluster_name [String] unique name (in Consul) for the Cassandra cluster
|
10
|
+
#
|
11
|
+
# @return [SeedRegistry]
|
12
|
+
#
|
13
|
+
def initialize cluster_name
|
14
|
+
@cluster_name = cluster_name.to_s
|
15
|
+
raise ArgumentError.new('cluster_name must not be empty') if @cluster_name.empty?
|
16
|
+
@semaphore = nil
|
17
|
+
@renew_thread = nil
|
18
|
+
@nodetool_info_cache = nil
|
19
|
+
end
|
20
|
+
|
21
|
+
# Schedule the seed registration process to run periodically
|
22
|
+
#
|
23
|
+
def schedule
|
24
|
+
[:interval, '10s']
|
25
|
+
end
|
26
|
+
|
27
|
+
# Get a lock in Consul registering the Cassandra node as a seed
|
28
|
+
#
|
29
|
+
def run!
|
30
|
+
@nodetool_info_cache = nil
|
31
|
+
if can_seed?
|
32
|
+
try_get_seed_lock
|
33
|
+
else
|
34
|
+
release_seed_lock
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Return true if the Cassandra node is a valid seed, false otherwise
|
39
|
+
#
|
40
|
+
# @return [Boolean]
|
41
|
+
#
|
42
|
+
def can_seed?
|
43
|
+
return false unless state == :normal
|
44
|
+
|
45
|
+
results = (nodetool_info_cached || '').split("\n")
|
46
|
+
results.map! { |line| line.strip }
|
47
|
+
|
48
|
+
filter_results = lambda do |key|
|
49
|
+
potential = results.select { |line| line.include? key }
|
50
|
+
potential.map! { |line| line.split(':')[1] }
|
51
|
+
potential.compact!
|
52
|
+
potential.size == 1 && potential.first.strip == 'true'
|
53
|
+
end
|
54
|
+
|
55
|
+
return false unless filter_results.call('Gossip active')
|
56
|
+
return false unless filter_results.call('Thrift active')
|
57
|
+
return false unless filter_results.call('Native Transport active')
|
58
|
+
|
59
|
+
true
|
60
|
+
end
|
61
|
+
|
62
|
+
# Return the state of the Cassandra node
|
63
|
+
#
|
64
|
+
# The returned state is reported by "nodetool netstats".
|
65
|
+
#
|
66
|
+
# @return [state, nil]
|
67
|
+
#
|
68
|
+
def state
|
69
|
+
results = (nodetool_netstats || '').split("\n")
|
70
|
+
results.map! { |line| line.strip }
|
71
|
+
results.select! { |line| line.include? 'Mode:' }
|
72
|
+
results.map! { |line| line.split(':')[1] }
|
73
|
+
results.compact!
|
74
|
+
return nil if results.size != 1
|
75
|
+
results.first.strip.downcase.to_sym
|
76
|
+
end
|
77
|
+
|
78
|
+
# Return the data center the Cassandra node is in
|
79
|
+
#
|
80
|
+
# The returned data center is reported by "nodetool info".
|
81
|
+
#
|
82
|
+
# @return [String, nil]
|
83
|
+
#
|
84
|
+
def data_center
|
85
|
+
results = (nodetool_info_cached || '').split("\n")
|
86
|
+
results.map! { |line| line.strip }
|
87
|
+
results.select! { |line| line.include?('Data Center') }
|
88
|
+
results.map! { |line| line.split(':')[1] }
|
89
|
+
results.compact!
|
90
|
+
return nil if results.size != 1
|
91
|
+
results.first.strip
|
92
|
+
end
|
93
|
+
|
94
|
+
# Return the rack the Cassandra node is in
|
95
|
+
#
|
96
|
+
# The returned rack is reported by "nodetool info".
|
97
|
+
#
|
98
|
+
# @return [String, nil]
|
99
|
+
#
|
100
|
+
def rack
|
101
|
+
results = (nodetool_info_cached || '').split("\n")
|
102
|
+
results.map! { |line| line.strip }
|
103
|
+
results.select! { |line| line.include?('Rack') }
|
104
|
+
results.map! { |line| line.split(':')[1] }
|
105
|
+
results.compact!
|
106
|
+
return nil if results.size != 1
|
107
|
+
results.first.strip
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
# Run the "nodetool info" command and return the output
|
113
|
+
#
|
114
|
+
# @return [String, nil] Output from the "nodetool info" command
|
115
|
+
#
|
116
|
+
def nodetool_info
|
117
|
+
@nodetool_info ||= DaemonRunner::ShellOut.new(command: 'nodetool info', timeout: 300)
|
118
|
+
@nodetool_info.run!
|
119
|
+
@nodetool_info.stdout
|
120
|
+
end
|
121
|
+
|
122
|
+
# Return cached output from "nodetool info" command
|
123
|
+
#
|
124
|
+
# This will attempt to populate an empty cache by calling "nodetool info".
|
125
|
+
#
|
126
|
+
# @return [String, nil] Cached output from the "nodetool info" command
|
127
|
+
#
|
128
|
+
def nodetool_info_cached
|
129
|
+
@nodetool_info_cache ||= nodetool_info
|
130
|
+
end
|
131
|
+
|
132
|
+
# Run the "nodetool netstats" command and return the output
|
133
|
+
#
|
134
|
+
# @return [String, nil] Output from the "nodetool netstats" command
|
135
|
+
#
|
136
|
+
def nodetool_netstats
|
137
|
+
@nodetool_netstats ||= DaemonRunner::ShellOut.new(command: 'nodetool netstats', timeout: 300)
|
138
|
+
@nodetool_netstats.run!
|
139
|
+
@nodetool_netstats.stdout
|
140
|
+
end
|
141
|
+
|
142
|
+
# Try to get the lock in Consul for this node as a seed
|
143
|
+
#
|
144
|
+
def try_get_seed_lock
|
145
|
+
if @semaphore.nil?
|
146
|
+
name = "#{@cluster_name}/#{data_center}-#{rack}"
|
147
|
+
@semaphore = DaemonRunner::Semaphore.lock(name, 1)
|
148
|
+
end
|
149
|
+
|
150
|
+
if @renew_thread.nil?
|
151
|
+
@renew_thread = @semaphore.renew
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
# Release the lock in Consul for this node as a seed
|
156
|
+
#
|
157
|
+
def release_seed_lock
|
158
|
+
unless @renew_thread.nil?
|
159
|
+
@renew_thread.kill
|
160
|
+
@renew_thread = nil
|
161
|
+
end
|
162
|
+
|
163
|
+
unless @semaphore.nil?
|
164
|
+
while @semaphore.locked?
|
165
|
+
@semaphore.try_release
|
166
|
+
sleep 0.1
|
167
|
+
end
|
168
|
+
@semaphore = nil
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
@@ -6,6 +6,7 @@ module Cassandra
|
|
6
6
|
|
7
7
|
def tasks
|
8
8
|
[
|
9
|
+
[seed_registry_task, 'run!'],
|
9
10
|
[auto_clean_task, 'run!'],
|
10
11
|
[health_stat, 'run!'],
|
11
12
|
[compaction_stat, 'run!'],
|
@@ -15,6 +16,11 @@ module Cassandra
|
|
15
16
|
|
16
17
|
private
|
17
18
|
|
19
|
+
def seed_registry_task
|
20
|
+
name = options[:seed_service_name]
|
21
|
+
@seed_registry_task ||= ::Cassandra::Tasks::SeedRegistry.new(name)
|
22
|
+
end
|
23
|
+
|
18
24
|
def auto_clean_task
|
19
25
|
@auto_clean_task ||= ::Cassandra::Tasks::Autoclean.new(options)
|
20
26
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cassandra-utils
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Thompson
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2017-06-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: mixlib-shellout
|
@@ -131,6 +131,7 @@ files:
|
|
131
131
|
- cassandra-utils.gemspec
|
132
132
|
- lib/cassandra/tasks.rb
|
133
133
|
- lib/cassandra/tasks/autoclean.rb
|
134
|
+
- lib/cassandra/tasks/seedregistry.rb
|
134
135
|
- lib/cassandra/utils.rb
|
135
136
|
- lib/cassandra/utils/cli/base.rb
|
136
137
|
- lib/cassandra/utils/daemon.rb
|