zookeeper 0.4.4 → 0.9.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. data/.gitignore +10 -0
  2. data/CHANGELOG +95 -0
  3. data/Gemfile +17 -0
  4. data/Manifest +8 -2
  5. data/README.markdown +59 -0
  6. data/Rakefile +137 -7
  7. data/ext/.gitignore +6 -0
  8. data/ext/Rakefile +51 -0
  9. data/ext/c_zookeeper.rb +212 -0
  10. data/ext/dbg.h +53 -0
  11. data/ext/depend +5 -0
  12. data/ext/extconf.rb +44 -15
  13. data/ext/generate_gvl_code.rb +316 -0
  14. data/ext/zkc-3.3.5.tar.gz +0 -0
  15. data/ext/zkrb_wrapper.c +731 -0
  16. data/ext/zkrb_wrapper.h +330 -0
  17. data/ext/zkrb_wrapper_compat.c +15 -0
  18. data/ext/zkrb_wrapper_compat.h +11 -0
  19. data/ext/zookeeper_base.rb +211 -0
  20. data/ext/zookeeper_c.c +268 -97
  21. data/ext/zookeeper_lib.c +157 -92
  22. data/ext/zookeeper_lib.h +12 -6
  23. data/java/zookeeper_base.rb +477 -0
  24. data/lib/zookeeper/acls.rb +10 -1
  25. data/lib/zookeeper/callbacks.rb +5 -3
  26. data/lib/zookeeper/common/queue_with_pipe.rb +78 -0
  27. data/lib/zookeeper/common.rb +174 -0
  28. data/lib/zookeeper/constants.rb +31 -28
  29. data/lib/zookeeper/em_client.rb +55 -0
  30. data/lib/zookeeper/exceptions.rb +10 -2
  31. data/lib/zookeeper/stat.rb +11 -2
  32. data/lib/zookeeper/version.rb +6 -0
  33. data/lib/zookeeper.rb +155 -122
  34. data/notes.txt +14 -0
  35. data/spec/c_zookeeper_spec.rb +50 -0
  36. data/spec/chrooted_connection_spec.rb +81 -0
  37. data/spec/default_watcher_spec.rb +41 -0
  38. data/spec/em_spec.rb +51 -0
  39. data/spec/log4j.properties +17 -0
  40. data/spec/shared/all_success_return_values.rb +10 -0
  41. data/spec/shared/connection_examples.rb +1018 -0
  42. data/spec/spec_helper.rb +119 -0
  43. data/spec/support/progress_formatter.rb +15 -0
  44. data/spec/zookeeper_spec.rb +24 -0
  45. data/zookeeper.gemspec +37 -25
  46. metadata +78 -34
  47. data/README +0 -42
  48. data/ext/zkc-3.3.2.tar.gz +0 -0
@@ -0,0 +1,477 @@
1
+ require 'java'
2
+ require 'thread'
3
+ require 'rubygems'
4
+
5
+ gem 'slyphon-log4j', '= 1.2.15'
6
+ gem 'slyphon-zookeeper_jar', '= 3.3.5'
7
+
8
+ require 'log4j'
9
+ require 'zookeeper_jar'
10
+
11
+ # The low-level wrapper-specific methods for the Java lib,
12
+ # subclassed by the top-level Zookeeper class
13
+ class ZookeeperBase
14
+ include Java
15
+ include ZookeeperCommon
16
+ include ZookeeperConstants
17
+ include ZookeeperCallbacks
18
+ include ZookeeperExceptions
19
+ include ZookeeperACLs
20
+ include ZookeeperStat
21
+
22
+ JZK = org.apache.zookeeper
23
+ JZKD = org.apache.zookeeper.data
24
+ Code = JZK::KeeperException::Code
25
+
26
+ ANY_VERSION = -1
27
+ DEFAULT_SESSION_TIMEOUT = 10_000
28
+
29
+ ZKRB_GLOBAL_CB_REQ = -1 unless defined?(ZKRB_GLOBAL_CB_REQ)
30
+
31
+ JZKD::Stat.class_eval do
32
+ MEMBERS = [:version, :czxid, :mzxid, :ctime, :mtime, :cversion, :aversion, :ephemeralOwner, :dataLength, :numChildren, :pzxid]
33
+ def to_hash
34
+ MEMBERS.inject({}) { |h,k| h[k] = __send__(k); h }
35
+ end
36
+ end
37
+
38
+ JZKD::Id.class_eval do
39
+ def to_hash
40
+ { :scheme => getScheme, :id => getId }
41
+ end
42
+ end
43
+
44
+ JZKD::ACL.class_eval do
45
+ def self.from_ruby_acl(acl)
46
+ raise TypeError, "acl must be a ZookeeperACLs::ACL not #{acl.inspect}" unless acl.kind_of?(ZookeeperACLs::ACL)
47
+ id = org.apache.zookeeper.data.Id.new(acl.id.scheme.to_s, acl.id.id.to_s)
48
+ new(acl.perms.to_i, id)
49
+ end
50
+
51
+ def to_hash
52
+ { :perms => getPerms, :id => getId.to_hash }
53
+ end
54
+ end
55
+
56
+ JZK::WatchedEvent.class_eval do
57
+ def to_hash
58
+ { :type => getType.getIntValue, :state => getState.getIntValue, :path => getPath }
59
+ end
60
+ end
61
+
62
+ # used for internal dispatching
63
+ module JavaCB #:nodoc:
64
+ class Callback
65
+ attr_reader :req_id
66
+
67
+ def initialize(req_id)
68
+ @req_id = req_id
69
+ end
70
+
71
+ protected
72
+ def logger
73
+ Zookeeper.logger
74
+ end
75
+ end
76
+
77
+ class DataCallback < Callback
78
+ include JZK::AsyncCallback::DataCallback
79
+
80
+ def processResult(rc, path, queue, data, stat)
81
+ logger.debug { "#{self.class.name}#processResult rc: #{rc}, req_id: #{req_id}, path: #{path}, queue: #{queue.inspect}, data: #{data.inspect}, stat: #{stat.inspect}" }
82
+
83
+ hash = {
84
+ :rc => rc,
85
+ :req_id => req_id,
86
+ :path => path,
87
+ :data => (data && String.from_java_bytes(data)),
88
+ :stat => (stat && stat.to_hash),
89
+ }
90
+
91
+ # if rc == Zookeeper::ZOK
92
+ # hash.merge!({
93
+ # :data => String.from_java_bytes(data),
94
+ # :stat => stat.to_hash,
95
+ # })
96
+ # end
97
+
98
+ queue.push(hash)
99
+ end
100
+ end
101
+
102
+ class StringCallback < Callback
103
+ include JZK::AsyncCallback::StringCallback
104
+
105
+ def processResult(rc, path, queue, str)
106
+ logger.debug { "#{self.class.name}#processResult rc: #{rc}, req_id: #{req_id}, path: #{path}, queue: #{queue.inspect}, str: #{str.inspect}" }
107
+ queue.push(:rc => rc, :req_id => req_id, :path => path, :string => str)
108
+ end
109
+ end
110
+
111
+ class StatCallback < Callback
112
+ include JZK::AsyncCallback::StatCallback
113
+
114
+ def processResult(rc, path, queue, stat)
115
+ logger.debug { "#{self.class.name}#processResult rc: #{rc.inspect}, req_id: #{req_id}, path: #{path.inspect}, queue: #{queue.inspect}, stat: #{stat.inspect}" }
116
+ queue.push(:rc => rc, :req_id => req_id, :stat => (stat and stat.to_hash), :path => path)
117
+ end
118
+ end
119
+
120
+ class Children2Callback < Callback
121
+ include JZK::AsyncCallback::Children2Callback
122
+
123
+ def processResult(rc, path, queue, children, stat)
124
+ logger.debug { "#{self.class.name}#processResult rc: #{rc}, req_id: #{req_id}, path: #{path}, queue: #{queue.inspect}, children: #{children.inspect}, stat: #{stat.inspect}" }
125
+ hash = {
126
+ :rc => rc,
127
+ :req_id => req_id,
128
+ :path => path,
129
+ :strings => (children && children.to_a),
130
+ :stat => (stat and stat.to_hash),
131
+ }
132
+
133
+ queue.push(hash)
134
+ end
135
+ end
136
+
137
+ class ACLCallback < Callback
138
+ include JZK::AsyncCallback::ACLCallback
139
+
140
+ def processResult(rc, path, queue, acl, stat)
141
+ logger.debug { "ACLCallback#processResult rc: #{rc.inspect}, req_id: #{req_id}, path: #{path.inspect}, queue: #{queue.inspect}, acl: #{acl.inspect}, stat: #{stat.inspect}" }
142
+ a = Array(acl).map { |a| a.to_hash }
143
+ queue.push(:rc => rc, :req_id => req_id, :path => path, :acl => a, :stat => (stat && stat.to_hash))
144
+ end
145
+ end
146
+
147
+ class VoidCallback < Callback
148
+ include JZK::AsyncCallback::VoidCallback
149
+
150
+ def processResult(rc, path, queue)
151
+ logger.debug { "#{self.class.name}#processResult rc: #{rc}, req_id: #{req_id}, queue: #{queue.inspect}" }
152
+ queue.push(:rc => rc, :req_id => req_id, :path => path)
153
+ end
154
+ end
155
+
156
+ class WatcherCallback < Callback
157
+ include JZK::Watcher
158
+
159
+ def initialize(event_queue)
160
+ @event_queue = event_queue
161
+ super(ZookeeperBase::ZKRB_GLOBAL_CB_REQ)
162
+ end
163
+
164
+ def process(event)
165
+ logger.debug { "WatcherCallback got event: #{event.to_hash.inspect}" }
166
+ hash = event.to_hash.merge(:req_id => req_id)
167
+ @event_queue.push(hash)
168
+ end
169
+ end
170
+ end
171
+
172
+ attr_reader :event_queue
173
+
174
+ def reopen(timeout=10, watcher=nil)
175
+ # watcher ||= @default_watcher
176
+
177
+ @mutex.synchronize do
178
+ # flushes all outstanding watcher reqs.
179
+ @watcher_reqs.clear
180
+ set_default_global_watcher
181
+
182
+ replace_jzk!
183
+ wait_until_connected
184
+ end
185
+
186
+ state
187
+ end
188
+
189
+ def wait_until_connected(timeout=10)
190
+ time_to_stop = timeout ? (Time.now + timeout) : nil
191
+
192
+ until connected? or (time_to_stop and Time.now > time_to_stop)
193
+ Thread.pass
194
+ end
195
+
196
+ connected?
197
+ end
198
+
199
+ def initialize(host, timeout=10, watcher=nil, options={})
200
+ @host = host
201
+ @event_queue = QueueWithPipe.new
202
+ @current_req_id = 0
203
+
204
+ @mutex = Monitor.new
205
+ @dispatch_shutdown_cond = @mutex.new_cond
206
+
207
+ @watcher_reqs = {}
208
+ @completion_reqs = {}
209
+ @_running = nil
210
+ @_closed = false
211
+ @options = {}
212
+
213
+ @default_watcher = (watcher || get_default_global_watcher)
214
+
215
+ # allows connected-state handlers to be registered before
216
+ yield self if block_given?
217
+
218
+ reopen(timeout)
219
+ return nil unless connected?
220
+ @_running = true
221
+ setup_dispatch_thread!
222
+ end
223
+
224
+ def close
225
+ shutdown_thread = Thread.new do
226
+ @mutex.synchronize do
227
+ unless @_closed
228
+ @_closed = true # these are probably unnecessary
229
+ @_running = false
230
+
231
+ stop_dispatch_thread!
232
+ @jzk.close if @jzk
233
+ end
234
+ end
235
+ end
236
+
237
+ shutdown_thread.join unless event_dispatch_thread?
238
+ end
239
+
240
+ def state
241
+ @mutex.synchronize { @jzk.state }
242
+ end
243
+
244
+ def connected?
245
+ state == JZK::ZooKeeper::States::CONNECTED
246
+ end
247
+
248
+ def connecting?
249
+ state == JZK::ZooKeeper::States::CONNECTING
250
+ end
251
+
252
+ def associating?
253
+ state == JZK::ZooKeeper::States::ASSOCIATING
254
+ end
255
+
256
+ def running?
257
+ @_running
258
+ end
259
+
260
+ def closed?
261
+ @_closed
262
+ end
263
+
264
+ def self.set_debug_level(*a)
265
+ # IGNORED IN JRUBY
266
+ end
267
+
268
+ def set_debug_level(*a)
269
+ # IGNORED IN JRUBY
270
+ end
271
+
272
+ def get(req_id, path, callback, watcher)
273
+ handle_keeper_exception do
274
+ watch_cb = watcher ? create_watcher(req_id, path) : false
275
+
276
+ if callback
277
+ jzk.getData(path, watch_cb, JavaCB::DataCallback.new(req_id), event_queue)
278
+ [Code::Ok, nil, nil] # the 'nil, nil' isn't strictly necessary here
279
+ else # sync
280
+ stat = JZKD::Stat.new
281
+ data = String.from_java_bytes(jzk.getData(path, watch_cb, stat))
282
+
283
+ [Code::Ok, data, stat.to_hash]
284
+ end
285
+ end
286
+ end
287
+
288
+ def set(req_id, path, data, callback, version)
289
+ handle_keeper_exception do
290
+ version ||= ANY_VERSION
291
+
292
+ if callback
293
+ jzk.setData(path, data.to_java_bytes, version, JavaCB::StatCallback.new(req_id), event_queue)
294
+ [Code::Ok, nil]
295
+ else
296
+ stat = jzk.setData(path, data.to_java_bytes, version).to_hash
297
+ [Code::Ok, stat]
298
+ end
299
+ end
300
+ end
301
+
302
+ def get_children(req_id, path, callback, watcher)
303
+ handle_keeper_exception do
304
+ watch_cb = watcher ? create_watcher(req_id, path) : false
305
+
306
+ if callback
307
+ jzk.getChildren(path, watch_cb, JavaCB::Children2Callback.new(req_id), event_queue)
308
+ [Code::Ok, nil, nil]
309
+ else
310
+ stat = JZKD::Stat.new
311
+ children = jzk.getChildren(path, watch_cb, stat)
312
+ [Code::Ok, children.to_a, stat.to_hash]
313
+ end
314
+ end
315
+ end
316
+
317
+ def create(req_id, path, data, callback, acl, flags)
318
+ handle_keeper_exception do
319
+ acl = Array(acl).map{ |a| JZKD::ACL.from_ruby_acl(a) }
320
+ mode = JZK::CreateMode.fromFlag(flags)
321
+
322
+ data ||= ''
323
+
324
+ if callback
325
+ jzk.create(path, data.to_java_bytes, acl, mode, JavaCB::StringCallback.new(req_id), event_queue)
326
+ [Code::Ok, nil]
327
+ else
328
+ new_path = jzk.create(path, data.to_java_bytes, acl, mode)
329
+ [Code::Ok, new_path]
330
+ end
331
+ end
332
+ end
333
+
334
+ def sync(req_id, path)
335
+ handle_keeper_exception do
336
+ jzk.sync(path, JavaCB::VoidCallback.new(req_id), event_queue)
337
+ Code::Ok
338
+ end
339
+ end
340
+
341
+ def delete(req_id, path, version, callback)
342
+ handle_keeper_exception do
343
+ if callback
344
+ jzk.delete(path, version, JavaCB::VoidCallback.new(req_id), event_queue)
345
+ else
346
+ jzk.delete(path, version)
347
+ end
348
+
349
+ Code::Ok
350
+ end
351
+ end
352
+
353
+ def set_acl(req_id, path, acl, callback, version)
354
+ handle_keeper_exception do
355
+ logger.debug { "set_acl: acl #{acl.inspect}" }
356
+ acl = Array(acl).flatten.map { |a| JZKD::ACL.from_ruby_acl(a) }
357
+ logger.debug { "set_acl: converted #{acl.inspect}" }
358
+
359
+ if callback
360
+ jzk.setACL(path, acl, version, JavaCB::ACLCallback.new(req_id), event_queue)
361
+ else
362
+ jzk.setACL(path, acl, version)
363
+ end
364
+
365
+ Code::Ok
366
+ end
367
+ end
368
+
369
+ def exists(req_id, path, callback, watcher)
370
+ handle_keeper_exception do
371
+ watch_cb = watcher ? create_watcher(req_id, path) : false
372
+
373
+ if callback
374
+ jzk.exists(path, watch_cb, JavaCB::StatCallback.new(req_id), event_queue)
375
+ [Code::Ok, nil, nil]
376
+ else
377
+ stat = jzk.exists(path, watch_cb)
378
+ [Code::Ok, (stat and stat.to_hash)]
379
+ end
380
+ end
381
+ end
382
+
383
+ def get_acl(req_id, path, callback)
384
+ handle_keeper_exception do
385
+ stat = JZKD::Stat.new
386
+
387
+ if callback
388
+ logger.debug { "calling getACL, path: #{path.inspect}, stat: #{stat.inspect}" }
389
+ jzk.getACL(path, stat, JavaCB::ACLCallback.new(req_id), event_queue)
390
+ [Code::Ok, nil, nil]
391
+ else
392
+ acls = jzk.getACL(path, stat).map { |a| a.to_hash }
393
+
394
+ [Code::Ok, Array(acls).map{|m| m.to_hash}, stat.to_hash]
395
+ end
396
+ end
397
+ end
398
+
399
+ def assert_open
400
+ # XXX don't know how to check for valid session state!
401
+ raise ZookeeperException::NotConnected unless connected?
402
+ end
403
+
404
+ # set the watcher object/proc that will receive all global events (such as session/state events)
405
+ #---
406
+ # XXX: this code needs to be duplicated from ext/zookeeper_base.rb because
407
+ # it's called from the initializer, and because of the C impl. we can't have
408
+ # the two decend from a common base, and a module wouldn't work
409
+ def set_default_global_watcher
410
+ @mutex.synchronize do
411
+ @watcher_reqs[ZKRB_GLOBAL_CB_REQ] = { :watcher => @default_watcher, :watcher_context => nil }
412
+ end
413
+ end
414
+
415
+ def session_id
416
+ jzk.session_id
417
+ end
418
+
419
+ def session_passwd
420
+ jzk.session_passwd.to_s
421
+ end
422
+
423
+ protected
424
+ def jzk
425
+ @mutex.synchronize { @jzk }
426
+ end
427
+
428
+ def handle_keeper_exception
429
+ yield
430
+ rescue JZK::KeeperException => e
431
+ e.cause.code.intValue
432
+ end
433
+
434
+ def call_type(callback, watcher)
435
+ if callback
436
+ watcher ? :async_watch : :async
437
+ else
438
+ watcher ? :sync_watch : :sync
439
+ end
440
+ end
441
+
442
+ def create_watcher(req_id, path)
443
+ logger.debug { "creating watcher for req_id: #{req_id} path: #{path}" }
444
+ lambda do |event|
445
+ logger.debug { "watcher for req_id #{req_id}, path: #{path} called back" }
446
+ h = { :req_id => req_id, :type => event.type.int_value, :state => event.state.int_value, :path => path }
447
+ event_queue.push(h)
448
+ end
449
+ end
450
+
451
+ # method to wait until block passed returns true or timeout (default is 10 seconds) is reached
452
+ def wait_until(timeout=10, &block)
453
+ time_to_stop = Time.now + timeout
454
+ until yield do
455
+ break if Time.now > time_to_stop
456
+ sleep 0.1
457
+ end
458
+ end
459
+
460
+ # TODO: Make all global puts configurable
461
+ def get_default_global_watcher
462
+ Proc.new { |args|
463
+ logger.debug { "Ruby ZK Global CB called type=#{event_by_value(args[:type])} state=#{state_by_value(args[:state])}" }
464
+ true
465
+ }
466
+ end
467
+
468
+ private
469
+ def replace_jzk!
470
+ orig_jzk = @jzk
471
+ @jzk = JZK::ZooKeeper.new(@host, DEFAULT_SESSION_TIMEOUT, JavaCB::WatcherCallback.new(event_queue))
472
+ ensure
473
+ orig_jzk.close if orig_jzk
474
+ end
475
+ end
476
+
477
+
@@ -5,13 +5,22 @@ module ZookeeperACLs
5
5
  @scheme = hash[:scheme]
6
6
  @id = hash[:id]
7
7
  end
8
+
9
+ def to_hash
10
+ { :id => id, :scheme => scheme }
11
+ end
8
12
  end
9
13
 
10
14
  class ACL
11
15
  attr_reader :perms, :id
12
16
  def initialize(hash)
13
17
  @perms = hash[:perms]
14
- @id = hash[:id]
18
+ v = hash[:id]
19
+ @id = v.kind_of?(Hash) ? Id.new(v) : v
20
+ end
21
+
22
+ def to_hash
23
+ { :perms => perms, :id => id.to_hash }
15
24
  end
16
25
  end
17
26
 
@@ -45,10 +45,12 @@ module ZookeeperCallbacks
45
45
 
46
46
  class StringCallback < Callback
47
47
  ## acreate, async
48
- attr_reader :return_code, :path
48
+ attr_reader :return_code, :string, :context
49
+
50
+ alias path string
49
51
 
50
52
  def initialize_context(hash)
51
- @return_code, @path, @context = hash[:rc], hash[:path], hash[:context]
53
+ @return_code, @string, @context = hash[:rc], hash[:string], hash[:context]
52
54
  end
53
55
  end
54
56
 
@@ -57,7 +59,7 @@ module ZookeeperCallbacks
57
59
  attr_reader :return_code, :children, :stat
58
60
 
59
61
  def initialize_context(hash)
60
- @return_code, @children, @stat, @context = hash[:rc], hash[:children], hash[:stat], hash[:context]
62
+ @return_code, @children, @stat, @context = hash[:rc], hash[:strings], hash[:stat], hash[:context]
61
63
  end
62
64
  end
63
65
 
@@ -0,0 +1,78 @@
1
+ module ZookeeperCommon
2
+ # Ceci n'est pas une pipe
3
+ class QueueWithPipe
4
+ extend Forwardable
5
+
6
+ def_delegators :@queue, :clear
7
+
8
+ # raised when close has been called, and pop() is performed
9
+ #
10
+ class ShutdownException < StandardError; end
11
+
12
+ # @private
13
+ KILL_TOKEN = Object.new unless defined?(KILL_TOKEN)
14
+
15
+ def initialize
16
+ # r, w = IO.pipe
17
+ # @pipe = { :read => r, :write => w }
18
+ @queue = Queue.new
19
+
20
+ # with the EventMachine client, we want to let EM handle clearing the
21
+ # event pipe, so we set this to false
22
+ # @clear_reads_on_pop = true
23
+
24
+ @mutex = Mutex.new
25
+ @closed = false
26
+ @graceful = false
27
+ end
28
+
29
+ def push(obj)
30
+ logger.debug { "#{self.class}##{__method__} obj: #{obj.inspect}, kill_token? #{obj == KILL_TOKEN}" }
31
+ @queue.push(obj)
32
+ end
33
+
34
+ def pop(non_blocking=false)
35
+ raise ShutdownException if closed? # this may get us in trouble
36
+
37
+ rv = @queue.pop(non_blocking)
38
+
39
+ if rv == KILL_TOKEN
40
+ close
41
+ raise ShutdownException
42
+ end
43
+
44
+ rv
45
+ end
46
+
47
+ # close the queue and causes ShutdownException to be raised on waiting threads
48
+ def graceful_close!
49
+ @mutex.synchronize do
50
+ return if @graceful or @closed
51
+ logger.debug { "#{self.class}##{__method__} gracefully closing" }
52
+ @graceful = true
53
+ push(KILL_TOKEN)
54
+ end
55
+ nil
56
+ end
57
+
58
+ def close
59
+ @mutex.synchronize do
60
+ return if @closed
61
+ @closed = true
62
+ end
63
+ end
64
+
65
+ def closed?
66
+ @mutex.synchronize { !!@closed }
67
+ end
68
+
69
+ private
70
+ def clear_reads_on_pop?
71
+ @clear_reads_on_pop
72
+ end
73
+
74
+ def logger
75
+ Zookeeper.logger
76
+ end
77
+ end
78
+ end