zk 0.6.5 → 0.7.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.dotfiles/rspec-logging +4 -0
- data/.gitignore +2 -0
- data/.yardopts +8 -0
- data/Gemfile +6 -1
- data/README.markdown +86 -0
- data/lib/z_k/client/base.rb +692 -0
- data/lib/z_k/client/conveniences.rb +134 -0
- data/lib/z_k/client/state_mixin.rb +94 -0
- data/lib/z_k/client/unixisms.rb +89 -0
- data/lib/z_k/client.rb +12 -891
- data/lib/z_k/election.rb +3 -0
- data/lib/z_k/event_handler.rb +7 -5
- data/lib/z_k/mongoid.rb +1 -1
- data/lib/z_k/pool.rb +70 -27
- data/lib/z_k/threadpool.rb +7 -2
- data/lib/z_k/version.rb +1 -1
- data/lib/z_k.rb +1 -2
- data/spec/spec_helper.rb +1 -0
- data/spec/support/logging_progress_bar_formatter.rb +14 -0
- data/spec/watch_spec.rb +26 -8
- data/spec/{client_spec.rb → z_k/client_spec.rb} +1 -1
- data/spec/{election_spec.rb → z_k/election_spec.rb} +2 -3
- data/spec/{locker_spec.rb → z_k/locker_spec.rb} +1 -1
- data/spec/{mongoid_spec.rb → z_k/mongoid_spec.rb} +1 -1
- data/spec/{client_pool_spec.rb → z_k/pool_spec.rb} +98 -126
- data/spec/{threadpool_spec.rb → z_k/threadpool_spec.rb} +6 -3
- data/zk.gemspec +1 -0
- metadata +37 -26
data/lib/z_k/client.rb
CHANGED
@@ -2,19 +2,13 @@ module ZK
|
|
2
2
|
# A ruby-friendly wrapper around the low-level zookeeper drivers. This is the
|
3
3
|
# class that you will likely interact with the most.
|
4
4
|
#
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
# @todo ACL support is pretty much unused currently.
|
6
|
+
# If anyone has suggestions, hints, use-cases, examples, etc. by all means please file a bug.
|
7
|
+
#
|
8
|
+
module Client
|
8
9
|
DEFAULT_TIMEOUT = 10
|
9
10
|
|
10
|
-
|
11
|
-
|
12
|
-
attr_reader :cnx #:nodoc:
|
13
|
-
|
14
|
-
# for backwards compatibility
|
15
|
-
alias :watcher :event_handler #:nodoc:
|
16
|
-
|
17
|
-
#:stopdoc:
|
11
|
+
# @private
|
18
12
|
STATE_SYM_MAP = {
|
19
13
|
Zookeeper::ZOO_CLOSED_STATE => :closed,
|
20
14
|
Zookeeper::ZOO_EXPIRED_SESSION_STATE => :expired_session,
|
@@ -23,888 +17,15 @@ module ZK
|
|
23
17
|
Zookeeper::ZOO_CONNECTED_STATE => :connected,
|
24
18
|
Zookeeper::ZOO_ASSOCIATING_STATE => :associating,
|
25
19
|
}.freeze
|
26
|
-
#:startdoc:
|
27
|
-
|
28
|
-
# Create a new client and connect to the zookeeper server.
|
29
|
-
#
|
30
|
-
# +host+ should be a string of comma-separated host:port pairs. You can
|
31
|
-
# also supply an optional "chroot" suffix that will act as an implicit
|
32
|
-
# prefix to all paths supplied.
|
33
|
-
#
|
34
|
-
# example:
|
35
|
-
#
|
36
|
-
# ZK::Client.new("zk01:2181,zk02:2181/chroot/path")
|
37
|
-
#
|
38
|
-
def initialize(host, opts={})
|
39
|
-
@event_handler = EventHandler.new(self)
|
40
|
-
yield self if block_given?
|
41
|
-
@cnx = ::Zookeeper.new(host, DEFAULT_TIMEOUT, @event_handler.get_default_watcher_block)
|
42
|
-
@threadpool = Threadpool.new
|
43
|
-
end
|
44
|
-
|
45
|
-
# Queue an operation to be run on an internal threadpool. You may either
|
46
|
-
# provide an object that responds_to?(:call) or pass a block. There is no
|
47
|
-
# mechanism for retrieving the result of the operation, it is purely
|
48
|
-
# fire-and-forget, so the user is expected to make arrangements for this in
|
49
|
-
# their code.
|
50
|
-
#
|
51
|
-
# An ArgumentError will be raised if +callable+ does not <tt>respond_to?(:call)</tt>
|
52
|
-
#
|
53
|
-
# ==== Arguments
|
54
|
-
# * <tt>callable</tt>: an object that <tt>respond_to?(:call)</tt>, takes precedence
|
55
|
-
# over a given block
|
56
|
-
#
|
57
|
-
def defer(callable=nil, &block)
|
58
|
-
@threadpool.defer(callable, &block)
|
59
|
-
end
|
60
|
-
|
61
|
-
# returns true if the connection has been closed
|
62
|
-
#--
|
63
|
-
# XXX: should this be *our* idea of closed or ZOO_CLOSED_STATE ?
|
64
|
-
def closed?
|
65
|
-
defined?(::JRUBY_VERSION) ? jruby_closed? : mri_closed?
|
66
|
-
end
|
67
|
-
|
68
|
-
private
|
69
|
-
def jruby_closed?
|
70
|
-
@cnx.state == Java::OrgApacheZookeeper::ZooKeeper::States::CLOSED
|
71
|
-
end
|
72
|
-
|
73
|
-
def mri_closed?
|
74
|
-
@cnx.state or false
|
75
|
-
rescue RuntimeError => e
|
76
|
-
# gah, lame error parsing here
|
77
|
-
raise e if (e.message != 'zookeeper handle is closed') and not defined?(::JRUBY_VERSION)
|
78
|
-
true
|
79
|
-
end
|
80
|
-
|
81
|
-
public
|
82
|
-
|
83
|
-
# returns the current state of the connection as reported by the underlying driver
|
84
|
-
# as a symbol. The possible values are <tt>[:closed, :expired_session, :auth_failed
|
85
|
-
# :connecting, :connected, :associating]</tt>.
|
86
|
-
#
|
87
|
-
# See the Zookeeper session
|
88
|
-
# {documentation}[http://hadoop.apache.org/zookeeper/docs/current/zookeeperProgrammers.html#ch_zkSessions]
|
89
|
-
# for more information
|
90
|
-
#
|
91
|
-
def state
|
92
|
-
if defined?(::JRUBY_VERSION)
|
93
|
-
@cnx.state.to_string.downcase.to_sym
|
94
|
-
else
|
95
|
-
STATE_SYM_MAP.fetch(@cnx.state) { |k| raise IndexError, "unrecognized state: #{k}" }
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
# reopen the underlying connection
|
100
|
-
# returns state of connection after operation
|
101
|
-
def reopen(timeout=10)
|
102
|
-
@cnx.reopen(timeout, @event_handler.get_default_watcher_block)
|
103
|
-
@threadpool.start! # restart the threadpool if previously stopped by close!
|
104
|
-
state
|
105
|
-
end
|
106
|
-
|
107
|
-
# Returns true if the underlying connection is in the +connected+ state.
|
108
|
-
def connected?
|
109
|
-
wrap_state_closed_error { @cnx and @cnx.connected? }
|
110
|
-
end
|
111
|
-
|
112
|
-
# Returns true if the underlying connection is in the +associating+ state.
|
113
|
-
def associating?
|
114
|
-
wrap_state_closed_error { @cnx and @cnx.associating? }
|
115
|
-
end
|
116
|
-
|
117
|
-
# Returns true if the underlying connection is in the +connecting+ state.
|
118
|
-
def connecting?
|
119
|
-
wrap_state_closed_error { @cnx and @cnx.connecting? }
|
120
|
-
end
|
121
|
-
|
122
|
-
# Returns true if the underlying connection is in the +expired_session+ state.
|
123
|
-
def expired_session?
|
124
|
-
return nil unless @cnx
|
125
|
-
|
126
|
-
if defined?(::JRUBY_VERSION)
|
127
|
-
@cnx.state == Java::OrgApacheZookeeper::ZooKeeper::States::EXPIRED_SESSION
|
128
|
-
else
|
129
|
-
wrap_state_closed_error { @cnx.state == Zookeeper::ZOO_EXPIRED_SESSION_STATE }
|
130
|
-
end
|
131
|
-
end
|
132
|
-
|
133
|
-
# does a stat on '/', returns true or false
|
134
|
-
def ping? #:nodoc:
|
135
|
-
false unless connected?
|
136
|
-
false|stat('/')
|
137
|
-
rescue ZK::Exceptions::KeeperException
|
138
|
-
false
|
139
|
-
end
|
140
|
-
|
141
|
-
# Create a node with the given path. The node data will be the given data,
|
142
|
-
# and node acl will be the given acl. The path is returned.
|
143
|
-
#
|
144
|
-
# The ephemeral argument specifies whether the created node will be
|
145
|
-
# ephemeral or not.
|
146
|
-
#
|
147
|
-
# An ephemeral node will be removed by the server automatically when the
|
148
|
-
# session associated with the creation of the node expires.
|
149
|
-
#
|
150
|
-
# The sequence argument can also specify to create a sequential node. The
|
151
|
-
# actual path name of a sequential node will be the given path plus a
|
152
|
-
# suffix "_i" where i is the current sequential number of the node. Once
|
153
|
-
# such a node is created, the sequential number will be incremented by one.
|
154
|
-
#
|
155
|
-
# If a node with the same actual path already exists in the ZooKeeper, a
|
156
|
-
# KeeperException with error code KeeperException::NodeExists will be
|
157
|
-
# thrown. Note that since a different actual path is used for each
|
158
|
-
# invocation of creating sequential node with the same path argument, the
|
159
|
-
# call will never throw a NodeExists KeeperException.
|
160
|
-
#
|
161
|
-
# If the parent node does not exist in the ZooKeeper, a KeeperException
|
162
|
-
# with error code KeeperException::NoNode will be thrown.
|
163
|
-
#
|
164
|
-
# An ephemeral node cannot have children. If the parent node of the given
|
165
|
-
# path is ephemeral, a KeeperException with error code
|
166
|
-
# KeeperException::NoChildrenForEphemerals will be thrown.
|
167
|
-
#
|
168
|
-
# This operation, if successful, will trigger all the watches left on the
|
169
|
-
# node of the given path by exists and get API calls, and the watches left
|
170
|
-
# on the parent node by children API calls.
|
171
|
-
#
|
172
|
-
# If a node is created successfully, the ZooKeeper server will trigger the
|
173
|
-
# watches on the path left by exists calls, and the watches on the parent
|
174
|
-
# of the node by children calls.
|
175
|
-
#
|
176
|
-
# Called with a hash of arguments set. Supports being executed
|
177
|
-
# asynchronousy by passing a callback object.
|
178
|
-
#
|
179
|
-
# ==== Arguments
|
180
|
-
# * <tt>path</tt> -- path of the node
|
181
|
-
# * <tt>data</tt> -- initial data for the node, defaults to an empty string
|
182
|
-
# * <tt>:acl</tt> -- defaults to <tt>ACL::OPEN_ACL_UNSAFE</tt>, otherwise the ACL for the node
|
183
|
-
# * <tt>:ephemeral</tt> -- defaults to false, if set to true the created node will be ephemeral
|
184
|
-
# * <tt>:sequence</tt> -- defaults to false, if set to true the created node will be sequential
|
185
|
-
# * <tt>:callback</tt> -- provide a AsyncCallback::StringCallback object or
|
186
|
-
# Proc for an asynchronous call to occur
|
187
|
-
# * <tt>:context</tt> -- context object passed into callback method
|
188
|
-
# * <tt>:mode</tt> -- may be specified instead of :ephemeral and :sequence,
|
189
|
-
# accepted values are <tt>[:ephemeral_sequential, :persistent_sequential,
|
190
|
-
# :persistent, :ephemeral]</tt>
|
191
|
-
#
|
192
|
-
# ==== Examples
|
193
|
-
#
|
194
|
-
# ===== create node, no data, persistent
|
195
|
-
#
|
196
|
-
# zk.create("/path")
|
197
|
-
# # => "/path"
|
198
|
-
#
|
199
|
-
# ===== create node, ACL will default to ACL::OPEN_ACL_UNSAFE
|
200
|
-
#
|
201
|
-
# zk.create("/path", "foo")
|
202
|
-
# # => "/path"
|
203
|
-
#
|
204
|
-
# ===== create ephemeral node
|
205
|
-
# zk.create("/path", :mode => :ephemeral)
|
206
|
-
# # => "/path"
|
207
|
-
#
|
208
|
-
# ===== create sequential node
|
209
|
-
# zk.create("/path", :mode => :persistent_sequence)
|
210
|
-
# # => "/path0"
|
211
|
-
#
|
212
|
-
# ===== create ephemeral and sequential node
|
213
|
-
# zk.create("/path", "foo", :mode => :ephemeral_sequence)
|
214
|
-
# # => "/path0"
|
215
|
-
#
|
216
|
-
# ===== create a child path
|
217
|
-
# zk.create("/path/child", "bar")
|
218
|
-
# # => "/path/child"
|
219
|
-
#
|
220
|
-
# ===== create a sequential child path
|
221
|
-
# zk.create("/path/child", "bar", :mode => :ephemeral_sequence)
|
222
|
-
# # => "/path/child0"
|
223
|
-
#
|
224
|
-
#--
|
225
|
-
# TODO: document asynchronous callback
|
226
|
-
#
|
227
|
-
# ===== create asynchronously with callback object
|
228
|
-
#
|
229
|
-
# class StringCallback
|
230
|
-
# def process_result(return_code, path, context, name)
|
231
|
-
# # do processing here
|
232
|
-
# end
|
233
|
-
# end
|
234
|
-
#
|
235
|
-
# callback = StringCallback.new
|
236
|
-
# context = Object.new
|
237
|
-
#
|
238
|
-
# zk.create("/path", "foo", :callback => callback, :context => context)
|
239
|
-
#
|
240
|
-
# ===== create asynchronously with callback proc
|
241
|
-
#
|
242
|
-
# callback = proc do |return_code, path, context, name|
|
243
|
-
# # do processing here
|
244
|
-
# end
|
245
|
-
#
|
246
|
-
# context = Object.new
|
247
|
-
#
|
248
|
-
# zk.create("/path", "foo", :callback => callback, :context => context)
|
249
|
-
#
|
250
|
-
#++
|
251
|
-
def create(path, data='', opts={})
|
252
|
-
h = { :path => path, :data => data, :ephemeral => false, :sequence => false }.merge(opts)
|
253
|
-
|
254
|
-
if mode = h.delete(:mode)
|
255
|
-
mode = mode.to_sym
|
256
|
-
|
257
|
-
case mode
|
258
|
-
when :ephemeral_sequential
|
259
|
-
h[:ephemeral] = h[:sequence] = true
|
260
|
-
when :persistent_sequential
|
261
|
-
h[:ephemeral] = false
|
262
|
-
h[:sequence] = true
|
263
|
-
when :persistent
|
264
|
-
h[:ephemeral] = false
|
265
|
-
when :ephemeral
|
266
|
-
h[:ephemeral] = true
|
267
|
-
else
|
268
|
-
raise ArgumentError, "Unknown mode: #{mode.inspect}"
|
269
|
-
end
|
270
|
-
end
|
271
|
-
|
272
|
-
rv = check_rc(@cnx.create(h), h)
|
273
|
-
|
274
|
-
h[:callback] ? rv : rv[:path]
|
275
|
-
end
|
276
|
-
|
277
|
-
# Return the data and stat of the node of the given path.
|
278
|
-
#
|
279
|
-
# If the watch is true and the call is successfull (no exception is
|
280
|
-
# thrown), a watch will be left on the node with the given path. The watch
|
281
|
-
# will be triggered by a successful operation that sets data on the node,
|
282
|
-
# or deletes the node. See +watcher+ for documentation on how to register
|
283
|
-
# blocks to be called when a watch event is fired.
|
284
|
-
#
|
285
|
-
# A KeeperException with error code KeeperException::NoNode will be thrown
|
286
|
-
# if no node with the given path exists.
|
287
|
-
#
|
288
|
-
# Supports being executed asynchronousy by passing a callback object.
|
289
|
-
#
|
290
|
-
# ==== Arguments
|
291
|
-
# * <tt>path</tt> -- path of the node
|
292
|
-
# * <tt>:watch</tt> -- defaults to false, set to true if you need to watch this node
|
293
|
-
# * <tt>:callback</tt> -- provide a AsyncCallback::DataCallback object or
|
294
|
-
# Proc for an asynchronous call to occur
|
295
|
-
# * <tt>:context</tt> -- context object passed into callback method
|
296
|
-
#
|
297
|
-
# ==== Examples
|
298
|
-
# ===== get data for path
|
299
|
-
# zk.get("/path")
|
300
|
-
#
|
301
|
-
# ===== get data and set watch on node
|
302
|
-
# zk.get("/path", :watch => true)
|
303
|
-
#
|
304
|
-
#--
|
305
|
-
# ===== get data asynchronously
|
306
|
-
#
|
307
|
-
# class DataCallback
|
308
|
-
# def process_result(return_code, path, context, data, stat)
|
309
|
-
# # do processing here
|
310
|
-
# end
|
311
|
-
# end
|
312
|
-
#
|
313
|
-
# zk.get("/path") do |return_code, path, context, data, stat|
|
314
|
-
# # do processing here
|
315
|
-
# end
|
316
|
-
#
|
317
|
-
# callback = DataCallback.new
|
318
|
-
# context = Object.new
|
319
|
-
# zk.get("/path", :callback => callback, :context => context)
|
320
|
-
#++
|
321
|
-
def get(path, opts={})
|
322
|
-
h = { :path => path }.merge(opts)
|
323
|
-
|
324
|
-
setup_watcher!(:data, h)
|
325
|
-
|
326
|
-
rv = check_rc(@cnx.get(h), h)
|
327
|
-
|
328
|
-
opts[:callback] ? rv : rv.values_at(:data, :stat)
|
329
|
-
end
|
330
|
-
|
331
|
-
# Set the data for the node of the given path if such a node exists and the
|
332
|
-
# given version matches the version of the node (if the given version is
|
333
|
-
# -1, it matches any node's versions). Return the stat of the node.
|
334
|
-
#
|
335
|
-
# This operation, if successful, will trigger all the watches on the node
|
336
|
-
# of the given path left by get_data calls.
|
337
|
-
#
|
338
|
-
# A KeeperException with error code KeeperException::NoNode will be thrown
|
339
|
-
# if no node with the given path exists. A KeeperException with error code
|
340
|
-
# KeeperException::BadVersion will be thrown if the given version does not
|
341
|
-
# match the node's version.
|
342
|
-
#
|
343
|
-
# Called with a hash of arguments set. Supports being executed
|
344
|
-
# asynchronousy by passing a callback object.
|
345
|
-
#
|
346
|
-
# ==== Arguments
|
347
|
-
# * <tt>:path</tt> -- path of the node
|
348
|
-
# * <tt>:data</tt> -- data to set
|
349
|
-
# * <tt>:version</tt> -- defaults to -1, otherwise set to the expected matching version
|
350
|
-
# * <tt>:callback</tt> -- provide a AsyncCallback::StatCallback object or
|
351
|
-
# Proc for an asynchronous call to occur
|
352
|
-
# * <tt>:context</tt> -- context object passed into callback method
|
353
|
-
#
|
354
|
-
# ==== Examples
|
355
|
-
# zk.set("/path", "foo")
|
356
|
-
# zk.set("/path", "foo", :version => 0)
|
357
|
-
#
|
358
|
-
#--
|
359
|
-
# ===== set data asynchronously
|
360
|
-
#
|
361
|
-
# class StatCallback
|
362
|
-
# def process_result(return_code, path, context, stat)
|
363
|
-
# # do processing here
|
364
|
-
# end
|
365
|
-
# end
|
366
|
-
#
|
367
|
-
# callback = StatCallback.new
|
368
|
-
# context = Object.new
|
369
|
-
#
|
370
|
-
# zk.set("/path", "foo", :callback => callback, :context => context)
|
371
|
-
#++
|
372
|
-
def set(path, data, opts={})
|
373
|
-
h = { :path => path, :data => data }.merge(opts)
|
374
|
-
|
375
|
-
rv = check_rc(@cnx.set(h), h)
|
376
20
|
|
377
|
-
|
21
|
+
def self.new(*a, &b)
|
22
|
+
Base.new(*a, &b)
|
378
23
|
end
|
379
|
-
|
380
|
-
# Return the stat of the node of the given path. Return nil if the node
|
381
|
-
# doesn't exist.
|
382
|
-
#
|
383
|
-
# If the watch is true and the call is successful (no exception is thrown),
|
384
|
-
# a watch will be left on the node with the given path. The watch will be
|
385
|
-
# triggered by a successful operation that creates/delete the node or sets
|
386
|
-
# the data on the node.
|
387
|
-
#
|
388
|
-
# Can be called with just the path, otherwise a hash with the arguments
|
389
|
-
# set. Supports being executed asynchronousy by passing a callback object.
|
390
|
-
#
|
391
|
-
# ==== Arguments
|
392
|
-
# * <tt>path</tt> -- path of the node
|
393
|
-
# * <tt>:watch</tt> -- defaults to false, set to true if you need to watch
|
394
|
-
# this node
|
395
|
-
# * <tt>:callback</tt> -- provide a AsyncCallback::StatCallback object or
|
396
|
-
# Proc for an asynchronous call to occur
|
397
|
-
# * <tt>:context</tt> -- context object passed into callback method
|
398
|
-
#
|
399
|
-
# ==== Examples
|
400
|
-
# ===== exists for path
|
401
|
-
# zk.stat("/path")
|
402
|
-
# # => ZK::Stat
|
403
|
-
#
|
404
|
-
# ===== exists for path with watch set
|
405
|
-
# zk.stat("/path", :watch => true)
|
406
|
-
# # => ZK::Stat
|
407
|
-
#
|
408
|
-
# ===== exists for non existent path
|
409
|
-
# zk.stat("/non_existent_path")
|
410
|
-
# # => nil
|
411
|
-
#
|
412
|
-
#--
|
413
|
-
# ===== exist node asynchronously
|
414
|
-
#
|
415
|
-
# class StatCallback
|
416
|
-
# def process_result(return_code, path, context, stat)
|
417
|
-
# # do processing here
|
418
|
-
# end
|
419
|
-
# end
|
420
|
-
#
|
421
|
-
# callback = StatCallback.new
|
422
|
-
# context = Object.new
|
423
|
-
#
|
424
|
-
# zk.exists?("/path", :callback => callback, :context => context)
|
425
|
-
#++
|
426
|
-
def stat(path, opts={})
|
427
|
-
h = { :path => path }.merge(opts)
|
428
|
-
|
429
|
-
setup_watcher!(:data, h)
|
430
|
-
|
431
|
-
rv = @cnx.stat(h)
|
432
|
-
|
433
|
-
return rv if opts[:callback]
|
434
|
-
|
435
|
-
case rv[:rc]
|
436
|
-
when Zookeeper::ZOK, Zookeeper::ZNONODE
|
437
|
-
rv[:stat]
|
438
|
-
else
|
439
|
-
check_rc(rv, h) # throws the appropriate error
|
440
|
-
end
|
441
|
-
end
|
442
|
-
|
443
|
-
# sugar around stat
|
444
|
-
#
|
445
|
-
# ===== instead of
|
446
|
-
# zk.stat('/path').exists?
|
447
|
-
# # => true
|
448
|
-
#
|
449
|
-
# ===== you can do
|
450
|
-
# zk.exists?('/path')
|
451
|
-
# # => true
|
452
|
-
#
|
453
|
-
# this only works for the synchronous version of stat. for async version,
|
454
|
-
# this method will act *exactly* like stat
|
455
|
-
#
|
456
|
-
def exists?(path, opts={})
|
457
|
-
rv = stat(path, opts)
|
458
|
-
opts[:callback] ? rv : rv.exists?
|
459
|
-
end
|
460
|
-
|
461
|
-
# closes the underlying connection and deregisters all callbacks
|
462
|
-
def close!
|
463
|
-
@event_handler.clear!
|
464
|
-
wrap_state_closed_error { @cnx.close }
|
465
|
-
@threadpool.shutdown
|
466
|
-
nil
|
467
|
-
end
|
468
|
-
|
469
|
-
# Delete the node with the given path. The call will succeed if such a node
|
470
|
-
# exists, and the given version matches the node's version (if the given
|
471
|
-
# version is -1, it matches any node's versions).
|
472
|
-
#
|
473
|
-
# A KeeperException with error code KeeperException::NoNode will be thrown
|
474
|
-
# if the nodes does not exist.
|
475
|
-
#
|
476
|
-
# A KeeperException with error code KeeperException::BadVersion will be
|
477
|
-
# thrown if the given version does not match the node's version.
|
478
|
-
#
|
479
|
-
# A KeeperException with error code KeeperException::NotEmpty will be
|
480
|
-
# thrown if the node has children.
|
481
|
-
#
|
482
|
-
# This operation, if successful, will trigger all the watches on the node
|
483
|
-
# of the given path left by exists API calls, and the watches on the parent
|
484
|
-
# node left by children API calls.
|
485
|
-
#
|
486
|
-
# Can be called with just the path, otherwise a hash with the arguments
|
487
|
-
# set. Supports being executed asynchronousy by passing a callback object.
|
488
|
-
#
|
489
|
-
# ==== Arguments
|
490
|
-
# * <tt>path</tt> -- path of the node to be deleted
|
491
|
-
# * <tt>:version</tt> -- defaults to -1 (deletes any version), otherwise
|
492
|
-
# set to the expected matching version
|
493
|
-
# * <tt>:callback</tt> -- provide a AsyncCallback::VoidCallback object or
|
494
|
-
# Proc for an asynchronous call to occur
|
495
|
-
# * <tt>:context</tt> -- context object passed into callback method
|
496
|
-
#
|
497
|
-
# ==== Examples
|
498
|
-
# zk.delete("/path")
|
499
|
-
# zk.delete("/path", :version => 0)
|
500
|
-
#
|
501
|
-
#--
|
502
|
-
# ===== delete node asynchronously
|
503
|
-
#
|
504
|
-
# class VoidCallback
|
505
|
-
# def process_result(return_code, path, context)
|
506
|
-
# # do processing here
|
507
|
-
# end
|
508
|
-
# end
|
509
|
-
#
|
510
|
-
# callback = VoidCallback.new
|
511
|
-
# context = Object.new
|
512
|
-
#
|
513
|
-
# zk.delete(/path", :callback => callback, :context => context)
|
514
|
-
#++
|
515
|
-
def delete(path, opts={})
|
516
|
-
h = { :path => path, :version => -1 }.merge(opts)
|
517
|
-
rv = check_rc(@cnx.delete(h), h)
|
518
|
-
nil
|
519
|
-
end
|
520
|
-
|
521
|
-
# Return the list of the children of the node of the given path.
|
522
|
-
#
|
523
|
-
# If the watch is true and the call is successful (no exception is thrown),
|
524
|
-
# a watch will be left on the node with the given path. The watch will be
|
525
|
-
# triggered by a successful operation that deletes the node of the given
|
526
|
-
# path or creates/delete a child under the node. See +watcher+ for
|
527
|
-
# documentation on how to register blocks to be called when a watch event
|
528
|
-
# is fired.
|
529
|
-
#
|
530
|
-
# A KeeperException with error code KeeperException::NoNode will be thrown
|
531
|
-
# if no node with the given path exists.
|
532
|
-
#
|
533
|
-
# Can be called with just the path, otherwise a hash with the arguments
|
534
|
-
# set. Supports being executed asynchronousy by passing a callback object.
|
535
|
-
#
|
536
|
-
# ==== Arguments
|
537
|
-
# * <tt>path</tt> -- path of the node
|
538
|
-
# * <tt>:watch</tt> -- defaults to false, set to true if you need to watch
|
539
|
-
# this node
|
540
|
-
# * <tt>:callback</tt> -- provide a AsyncCallback::ChildrenCallback object
|
541
|
-
# or Proc for an asynchronous call to occur
|
542
|
-
# * <tt>:context</tt> -- context object passed into callback method
|
543
|
-
#
|
544
|
-
# ==== Examples
|
545
|
-
# ===== get children for path
|
546
|
-
# zk.create("/path", :data => "foo")
|
547
|
-
# zk.create("/path/child", :data => "child1", :sequence => true)
|
548
|
-
# zk.create("/path/child", :data => "child2", :sequence => true)
|
549
|
-
# zk.children("/path")
|
550
|
-
# # => ["child0", "child1"]
|
551
|
-
#
|
552
|
-
# ====== get children and set watch
|
553
|
-
# zk.children("/path", :watch => true)
|
554
|
-
# # => ["child0", "child1"]
|
555
|
-
#
|
556
|
-
#--
|
557
|
-
# ===== get children asynchronously
|
558
|
-
#
|
559
|
-
# class ChildrenCallback
|
560
|
-
# def process_result(return_code, path, context, children)
|
561
|
-
# # do processing here
|
562
|
-
# end
|
563
|
-
# end
|
564
|
-
#
|
565
|
-
# callback = ChildrenCallback.new
|
566
|
-
# context = Object.new
|
567
|
-
# zk.children("/path", :callback => callback, :context => context)
|
568
|
-
#++
|
569
|
-
def children(path, opts={})
|
570
|
-
h = { :path => path }.merge(opts)
|
571
|
-
|
572
|
-
setup_watcher!(:child, h)
|
573
|
-
|
574
|
-
rv = check_rc(@cnx.get_children(h), h)
|
575
|
-
opts[:callback] ? nil : rv[:children]
|
576
|
-
end
|
577
|
-
|
578
|
-
# Return the ACL and stat of the node of the given path.
|
579
|
-
#
|
580
|
-
# A KeeperException with error code KeeperException::Code::NoNode will be
|
581
|
-
# thrown if no node with the given path exists.
|
582
|
-
#
|
583
|
-
# Can be called with just the path, otherwise a hash with the arguments
|
584
|
-
# set. Supports being executed asynchronousy by passing a callback object.
|
585
|
-
#
|
586
|
-
# ==== Arguments
|
587
|
-
# * <tt>path</tt> -- path of the node
|
588
|
-
# * <tt>:stat</tt> -- defaults to nil, provide a Stat object that will be
|
589
|
-
# set with the Stat information of the node path (TODO: test this)
|
590
|
-
# * <tt>:callback</tt> -- provide a AsyncCallback::AclCallback object or
|
591
|
-
# Proc for an asynchronous call to occur
|
592
|
-
# * <tt>:context</tt> -- context object passed into callback method
|
593
|
-
#
|
594
|
-
# ==== Examples
|
595
|
-
# ===== get acl
|
596
|
-
# zk.get_acl("/path")
|
597
|
-
# # => [ACL]
|
598
|
-
#
|
599
|
-
# ===== get acl with stat
|
600
|
-
# stat = ZK::Stat.new
|
601
|
-
# zk.get_acl("/path", :stat => stat)
|
602
|
-
#
|
603
|
-
#--
|
604
|
-
# ===== get acl asynchronously
|
605
|
-
#
|
606
|
-
# class AclCallback
|
607
|
-
# def processResult(return_code, path, context, acl, stat)
|
608
|
-
# # do processing here
|
609
|
-
# end
|
610
|
-
# end
|
611
|
-
#
|
612
|
-
# callback = AclCallback.new
|
613
|
-
# context = Object.new
|
614
|
-
# zk.acls("/path", :callback => callback, :context => context)
|
615
|
-
#++
|
616
|
-
def get_acl(path, opts={})
|
617
|
-
h = { :path => path }.merge(opts)
|
618
|
-
rv = check_rc(@cnx.get_acl(h), h)
|
619
|
-
opts[:callback] ? nil : rv.values_at(:children, :stat)
|
620
|
-
end
|
621
|
-
|
622
|
-
# Set the ACL for the node of the given path if such a node exists and the
|
623
|
-
# given version matches the version of the node. Return the stat of the
|
624
|
-
# node.
|
625
|
-
#
|
626
|
-
# A KeeperException with error code KeeperException::Code::NoNode will be
|
627
|
-
# thrown if no node with the given path exists.
|
628
|
-
#
|
629
|
-
# A KeeperException with error code KeeperException::Code::BadVersion will
|
630
|
-
# be thrown if the given version does not match the node's version.
|
631
|
-
#
|
632
|
-
# Called with a hash of arguments set. Supports being executed
|
633
|
-
# asynchronousy by passing a callback object.
|
634
|
-
#
|
635
|
-
# ==== Arguments
|
636
|
-
# * <tt>path</tt> -- path of the node
|
637
|
-
# * <tt>:acl</tt> -- acl to set
|
638
|
-
# * <tt>:version</tt> -- defaults to -1, otherwise set to the expected matching version
|
639
|
-
# * <tt>:callback</tt> -- provide a AsyncCallback::StatCallback object or
|
640
|
-
# Proc for an asynchronous call to occur
|
641
|
-
# * <tt>:context</tt> -- context object passed into callback method
|
642
|
-
#
|
643
|
-
# ==== Examples
|
644
|
-
# TBA - waiting on clarification of method use
|
645
|
-
#
|
646
|
-
def set_acl(path, acls, opts={})
|
647
|
-
h = { :path => path, :acl => acls }.merge(opts)
|
648
|
-
rv = check_rc(@cnx.set_acl(h), h)
|
649
|
-
opts[:callback] ? nil : rv[:stat]
|
650
|
-
end
|
651
|
-
|
652
|
-
#--
|
653
|
-
#
|
654
|
-
# EXTENSIONS
|
655
|
-
#
|
656
|
-
# convenience methods for dealing with zookeeper (rm -rf, mkdir -p, etc)
|
657
|
-
#
|
658
|
-
#++
|
659
|
-
|
660
|
-
# Creates all parent paths and 'path' in zookeeper as persistent nodes with
|
661
|
-
# zero data.
|
662
|
-
#
|
663
|
-
# ==== Arguments
|
664
|
-
# * <tt>path</tt>: An absolute znode path to create
|
665
|
-
#
|
666
|
-
# ==== Examples
|
667
|
-
#
|
668
|
-
# zk.exists?('/path')
|
669
|
-
# # => false
|
670
|
-
#
|
671
|
-
# zk.mkdir_p('/path/to/blah')
|
672
|
-
# # => "/path/to/blah"
|
673
|
-
#
|
674
|
-
#--
|
675
|
-
# TODO: write a non-recursive version of this. ruby doesn't have TCO, so
|
676
|
-
# this could get expensive w/ psychotically long paths
|
677
|
-
def mkdir_p(path)
|
678
|
-
create(path, '', :mode => :persistent)
|
679
|
-
rescue Exceptions::NodeExists
|
680
|
-
return
|
681
|
-
rescue Exceptions::NoNode
|
682
|
-
if File.dirname(path) == '/'
|
683
|
-
# ok, we're screwed, blow up
|
684
|
-
raise KeeperException, "could not create '/', something is wrong", caller
|
685
|
-
end
|
686
|
-
|
687
|
-
mkdir_p(File.dirname(path))
|
688
|
-
retry
|
689
|
-
end
|
690
|
-
|
691
|
-
# recursively remove all children of path then remove path itself
|
692
|
-
def rm_rf(paths)
|
693
|
-
Array(paths).flatten.each do |path|
|
694
|
-
begin
|
695
|
-
children(path).each do |child|
|
696
|
-
rm_rf(File.join(path, child))
|
697
|
-
end
|
698
|
-
|
699
|
-
delete(path)
|
700
|
-
nil
|
701
|
-
rescue Exceptions::NoNode
|
702
|
-
end
|
703
|
-
end
|
704
|
-
end
|
705
|
-
|
706
|
-
# see ZK::Find for explanation
|
707
|
-
def find(*paths, &block)
|
708
|
-
ZK::Find.find(self, *paths, &block)
|
709
|
-
end
|
710
|
-
|
711
|
-
# will block the caller until +abs_node_path+ has been removed
|
712
|
-
#
|
713
|
-
# NOTE: this is dangerous to use in callbacks! there is only one
|
714
|
-
# event-delivery thread, so if you use this method in a callback or
|
715
|
-
# watcher, you *will* deadlock!
|
716
|
-
def block_until_node_deleted(abs_node_path)
|
717
|
-
queue = Queue.new
|
718
|
-
ev_sub = nil
|
719
|
-
|
720
|
-
node_deletion_cb = lambda do |event|
|
721
|
-
if event.node_deleted?
|
722
|
-
queue.enq(:deleted)
|
723
|
-
else
|
724
|
-
queue.enq(:deleted) unless exists?(abs_node_path, :watch => true)
|
725
|
-
end
|
726
|
-
end
|
727
|
-
|
728
|
-
ev_sub = watcher.register(abs_node_path, &node_deletion_cb)
|
729
|
-
|
730
|
-
# set up the callback, but bail if we don't need to wait
|
731
|
-
return true unless exists?(abs_node_path, :watch => true)
|
732
|
-
|
733
|
-
queue.pop # block waiting for node deletion
|
734
|
-
true
|
735
|
-
ensure
|
736
|
-
# be sure we clean up after ourselves
|
737
|
-
ev_sub.unregister if ev_sub
|
738
|
-
end
|
739
|
-
|
740
|
-
# creates a new locker based on the name you send in
|
741
|
-
#
|
742
|
-
# see ZK::Locker::ExclusiveLocker
|
743
|
-
#
|
744
|
-
# returns a ZK::Locker::ExclusiveLocker instance using this Client and provided
|
745
|
-
# lock name
|
746
|
-
#
|
747
|
-
# ==== Arguments
|
748
|
-
# * <tt>name</tt> name of the lock you wish to use
|
749
|
-
#
|
750
|
-
# ==== Examples
|
751
|
-
#
|
752
|
-
# zk.locker("blah")
|
753
|
-
# # => #<ZK::Locker::ExclusiveLocker:0x102034cf8 ...>
|
754
|
-
#
|
755
|
-
def locker(name)
|
756
|
-
Locker.exclusive_locker(self, name)
|
757
|
-
end
|
758
|
-
|
759
|
-
# create a new shared locking instance based on the name given
|
760
|
-
#
|
761
|
-
# returns a ZK::Locker::SharedLocker instance using this Client and provided
|
762
|
-
# lock name
|
763
|
-
#
|
764
|
-
# ==== Arguments
|
765
|
-
# * <tt>name</tt> name of the lock you wish to use
|
766
|
-
#
|
767
|
-
# ==== Examples
|
768
|
-
#
|
769
|
-
# zk.shared_locker("blah")
|
770
|
-
# # => #<ZK::Locker::SharedLocker:0x102034cf8 ...>
|
771
|
-
#
|
772
|
-
def shared_locker(name)
|
773
|
-
Locker.shared_locker(self, name)
|
774
|
-
end
|
775
|
-
|
776
|
-
# Convenience method for acquiring a lock then executing a code block. This
|
777
|
-
# will block the caller until the lock is acquired.
|
778
|
-
#
|
779
|
-
# ==== Arguments
|
780
|
-
# * <tt>name</tt>: the name of the lock to use
|
781
|
-
# * <tt>:mode</tt>: either :shared or :exclusive, defaults to :exclusive
|
782
|
-
#
|
783
|
-
# ==== Examples
|
784
|
-
#
|
785
|
-
# zk.with_lock('foo') do
|
786
|
-
# # this code is executed while holding the lock
|
787
|
-
# end
|
788
|
-
#
|
789
|
-
def with_lock(name, opts={}, &b)
|
790
|
-
mode = opts[:mode] || :exclusive
|
791
|
-
|
792
|
-
raise ArgumentError, ":mode option must be either :shared or :exclusive, not #{mode.inspect}" unless [:shared, :exclusive].include?(mode)
|
793
|
-
|
794
|
-
if mode == :shared
|
795
|
-
shared_locker(name).with_lock(&b)
|
796
|
-
else
|
797
|
-
locker(name).with_lock(&b)
|
798
|
-
end
|
799
|
-
end
|
800
|
-
|
801
|
-
# Convenience method for constructing a ZK::Election::Candidate object using this
|
802
|
-
# Client connection, the given election +name+ and +data+.
|
803
|
-
#
|
804
|
-
def election_candidate(name, data, opts={})
|
805
|
-
opts = opts.merge(:data => data)
|
806
|
-
ZK::Election::Candidate.new(self, name, opts)
|
807
|
-
end
|
808
|
-
|
809
|
-
# Convenience method for constructing a ZK::Election::Observer object using this
|
810
|
-
# Client connection, and the given election +name+.
|
811
|
-
#
|
812
|
-
def election_observer(name, opts={})
|
813
|
-
ZK::Election::Observer.new(self, name, opts)
|
814
|
-
end
|
815
|
-
|
816
|
-
# creates a new message queue of name +name+
|
817
|
-
#
|
818
|
-
# returns a ZK::MessageQueue object
|
819
|
-
#
|
820
|
-
# ==== Arguments
|
821
|
-
# * <tt>name</tt> the name of the queue
|
822
|
-
#
|
823
|
-
# ==== Examples
|
824
|
-
#
|
825
|
-
# zk.queue("blah").publish({:some_data => "that is yaml serializable"})
|
826
|
-
#
|
827
|
-
def queue(name)
|
828
|
-
MessageQueue.new(self, name)
|
829
|
-
end
|
830
|
-
|
831
|
-
def set_debug_level(level) #:nodoc:
|
832
|
-
if defined?(::JRUBY_VERSION)
|
833
|
-
warn "set_debug_level is not implemented for JRuby"
|
834
|
-
return
|
835
|
-
else
|
836
|
-
num =
|
837
|
-
case level
|
838
|
-
when String, Symbol
|
839
|
-
ZookeeperBase.const_get(:"ZOO_LOG_LEVEL_#{level.to_s.upcase}") rescue NameError
|
840
|
-
when Integer
|
841
|
-
level
|
842
|
-
end
|
843
|
-
|
844
|
-
raise ArgumentError, "#{level.inspect} is not a valid argument to set_debug_level" unless num
|
845
|
-
|
846
|
-
@cnx.set_debug_level(num)
|
847
|
-
end
|
848
|
-
end
|
849
|
-
|
850
|
-
# Register a block to be called on connection, when the client has
|
851
|
-
# connected. The block will *always* be called asynchronously (on a
|
852
|
-
# background thread).
|
853
|
-
#
|
854
|
-
# the block will be called with no arguments
|
855
|
-
#
|
856
|
-
# returns an EventHandlerSubscription object that can be used to unregister
|
857
|
-
# this block from further updates
|
858
|
-
#
|
859
|
-
def on_connected(&block)
|
860
|
-
watcher.register_state_handler(:connected, &block).tap do
|
861
|
-
defer { block.call } if connected?
|
862
|
-
end
|
863
|
-
end
|
864
|
-
|
865
|
-
# register a block to be called when the client is attempting to reconnect
|
866
|
-
# to the zookeeper server. the documentation says that this state should be
|
867
|
-
# taken to mean that the application should enter into "safe mode" and operate
|
868
|
-
# conservatively, as it won't be getting updates until it has reconnected
|
869
|
-
#
|
870
|
-
def on_connecting(&block)
|
871
|
-
watcher.register_state_handler(:connecting, &block).tap do
|
872
|
-
defer { block.call } if connecting?
|
873
|
-
end
|
874
|
-
end
|
875
|
-
|
876
|
-
# register a block to be called when our session has expired. This usually happens
|
877
|
-
# due to a network partitioning event, and means that all callbacks and watches must
|
878
|
-
# be re-registered with the server
|
879
|
-
#---
|
880
|
-
# NOTE: need to come up with a way to test this
|
881
|
-
def on_expired_session(&block)
|
882
|
-
watcher.register_state_handler(:expired_session, &block).tap do
|
883
|
-
defer { block.call } if expired_session?
|
884
|
-
end
|
885
|
-
end
|
886
|
-
|
887
|
-
protected
|
888
|
-
def wrap_state_closed_error
|
889
|
-
yield
|
890
|
-
rescue RuntimeError => e
|
891
|
-
# gah, lame error parsing here
|
892
|
-
raise e unless e.message == 'zookeeper handle is closed'
|
893
|
-
false
|
894
|
-
end
|
895
|
-
|
896
|
-
def check_rc(hash, inputs=nil)
|
897
|
-
hash.tap do |h|
|
898
|
-
if code = h[:rc]
|
899
|
-
msg = inputs ? "inputs: #{inputs.inspect}" : nil
|
900
|
-
raise Exceptions::KeeperException.by_code(code), msg unless code == Zookeeper::ZOK
|
901
|
-
end
|
902
|
-
end
|
903
|
-
end
|
904
|
-
|
905
|
-
def setup_watcher!(watch_type, opts)
|
906
|
-
@event_handler.setup_watcher!(watch_type, opts)
|
907
|
-
end
|
908
24
|
end
|
909
25
|
end
|
910
26
|
|
27
|
+
require 'z_k/client/state_mixin'
|
28
|
+
require 'z_k/client/unixisms'
|
29
|
+
require 'z_k/client/conveniences'
|
30
|
+
require 'z_k/client/base'
|
31
|
+
|