zk 0.6.5 → 0.7.1

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.
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
- class Client
6
- extend Forwardable
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
- attr_reader :event_handler
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
- opts[:callback] ? nil : rv[:stat]
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
+