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/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
+