cassandra-utils 0.3.2 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cbc1561d91999dc7f77a4e43dd4d747eae9207d9
4
- data.tar.gz: c24462312be9a71cf6e56df72658018e188e98ea
3
+ metadata.gz: 9e37957c030dfb9729a4d2b98df7db3acc002f68
4
+ data.tar.gz: b56baf5d6d2a88963a0803e1f0abfd1931ea7f77
5
5
  SHA512:
6
- metadata.gz: 8b3bde9c1bc2a93e32466e5ad5d016c5cfd1b2499dfc05d78144cc09331e015eb6f4595528cc24cb43597fbe346d9b9aa9414ae84fdc8d1e0fcbefabbe91c6db
7
- data.tar.gz: 6005912c05f287dcb04cc7815677873b40e14191032a9e2dd164dc22cb4606844fc057094d8387b681604ed98cf22f44073058c023ef03e50ecc7bc7c25665c0
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)
@@ -1 +1,2 @@
1
1
  require_relative 'tasks/autoclean'
2
+ require_relative 'tasks/seedregistry'
@@ -74,12 +74,24 @@ module Cassandra
74
74
  # Run the Cassandra cleanup process if necessary
75
75
  #
76
76
  def run!
77
- return unless status == :up
78
- return unless state == :normal
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
- return if new_tokens == old_tokens
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
- return [] unless data['version'] == ::Cassandra::Utils::VERSION
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
- return [] if tokens.nil?
101
- return [] unless tokens.respond_to? :each
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
- return [] if address.nil?
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.new(token_cache_path, 'w+')
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.3.2
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: 2016-12-02 00:00:00.000000000 Z
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