zkruby 3.4.3
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.tar.gz.sig +0 -0
- data/.gemtest +0 -0
- data/History.txt +18 -0
- data/Manifest.txt +39 -0
- data/README.rdoc +119 -0
- data/Rakefile +39 -0
- data/jute/jute.citrus +105 -0
- data/jute/lib/hoe/jute.rb +56 -0
- data/jute/lib/jute.rb +120 -0
- data/lib/em_zkruby.rb +4 -0
- data/lib/jute/zookeeper.rb +203 -0
- data/lib/zkruby.rb +4 -0
- data/lib/zkruby/bindata.rb +45 -0
- data/lib/zkruby/client.rb +608 -0
- data/lib/zkruby/enum.rb +108 -0
- data/lib/zkruby/eventmachine.rb +186 -0
- data/lib/zkruby/multi.rb +47 -0
- data/lib/zkruby/protocol.rb +182 -0
- data/lib/zkruby/rubyio.rb +310 -0
- data/lib/zkruby/session.rb +445 -0
- data/lib/zkruby/util.rb +141 -0
- data/lib/zkruby/zkruby.rb +27 -0
- data/spec/bindata_spec.rb +51 -0
- data/spec/enum_spec.rb +84 -0
- data/spec/eventmachine_spec.rb +50 -0
- data/spec/multi_spec.rb +93 -0
- data/spec/protocol_spec.rb +72 -0
- data/spec/recipe_helper.rb +68 -0
- data/spec/rubyio_spec.rb +8 -0
- data/spec/sequences_spec.rb +19 -0
- data/spec/server_helper.rb +45 -0
- data/spec/shared/auth.rb +40 -0
- data/spec/shared/basic.rb +180 -0
- data/spec/shared/binding.rb +33 -0
- data/spec/shared/chroot.rb +61 -0
- data/spec/shared/multi.rb +38 -0
- data/spec/shared/util.rb +56 -0
- data/spec/shared/watch.rb +49 -0
- data/spec/spec_helper.rb +12 -0
- data/src/jute/zookeeper.jute +288 -0
- data/yard_ext/enum_handler.rb +16 -0
- metadata +243 -0
- metadata.gz.sig +0 -0
data/lib/zkruby/enum.rb
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
|
2
|
+
# Including in a class and us the "enum" DSL method to create class instances
|
3
|
+
# As a special case, if the including class is descendant from StandardError, the enumerated
|
4
|
+
# instances will be classes descendent from the including class (eg like Errno)
|
5
|
+
module Enumeration
|
6
|
+
|
7
|
+
def self.included(base)
|
8
|
+
base.extend(ClassMethods)
|
9
|
+
# In the normal case we just include our methods in the base class
|
10
|
+
# but for Errors we need to include them on each instance
|
11
|
+
base.send :include,InstanceMethods unless base < StandardError
|
12
|
+
end
|
13
|
+
|
14
|
+
module InstanceMethods
|
15
|
+
def === (other)
|
16
|
+
case other
|
17
|
+
when Symbol
|
18
|
+
to_sym == other
|
19
|
+
when Fixnum
|
20
|
+
to_int == other
|
21
|
+
else
|
22
|
+
super
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_i
|
27
|
+
to_int
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_int
|
31
|
+
@index
|
32
|
+
end
|
33
|
+
|
34
|
+
def |(num)
|
35
|
+
to_int | num
|
36
|
+
end
|
37
|
+
|
38
|
+
def &(num)
|
39
|
+
to_int & num
|
40
|
+
end
|
41
|
+
|
42
|
+
def to_sym
|
43
|
+
@name
|
44
|
+
end
|
45
|
+
|
46
|
+
def to_s
|
47
|
+
"#{super} (:#{@name} [#{@index}])"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
module ClassMethods
|
52
|
+
|
53
|
+
# @param [] ref Symbol, Fixnum or Enumeration
|
54
|
+
# @return [Enumeration] instance representing ref or nil if not found
|
55
|
+
def get(ref)
|
56
|
+
@enums[ref]
|
57
|
+
end
|
58
|
+
|
59
|
+
# @param [] ref Symbol, Fixnum
|
60
|
+
# @raise [KeyError] if ref not found
|
61
|
+
# @return [Enumeration]
|
62
|
+
def fetch(ref)
|
63
|
+
@enums.fetch(ref)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Will dynamically create a new enum instance if ref is not found
|
67
|
+
def lookup(ref)
|
68
|
+
case ref
|
69
|
+
when Enumeration,Class
|
70
|
+
ref
|
71
|
+
when Symbol
|
72
|
+
get(ref) || enum(ref,nil)
|
73
|
+
when Fixnum
|
74
|
+
get(ref) || enum(nil,ref)
|
75
|
+
else
|
76
|
+
nil
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def enum(name,index,*args)
|
81
|
+
@enums ||= {}
|
82
|
+
|
83
|
+
instance = if (self < StandardError) then Class.new(self) else self.new(*args) end
|
84
|
+
|
85
|
+
instance.extend(InstanceMethods) if (self < StandardError)
|
86
|
+
|
87
|
+
const_name = if name.nil?
|
88
|
+
"ENUM" + index.to_s.sub("-","_")
|
89
|
+
else
|
90
|
+
name.to_s.upcase
|
91
|
+
end
|
92
|
+
|
93
|
+
self.const_set(const_name.to_s.upcase,instance)
|
94
|
+
|
95
|
+
@enums[instance] = instance
|
96
|
+
@enums[name] = instance if name
|
97
|
+
@enums[index] = instance if index
|
98
|
+
instance.instance_variable_set(:@name,name)
|
99
|
+
instance.instance_variable_set(:@index,index)
|
100
|
+
instance.freeze
|
101
|
+
end
|
102
|
+
|
103
|
+
def method_missing(method,*args)
|
104
|
+
@enums[method.downcase] || super
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,186 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
|
3
|
+
if defined?(JRUBY_VERSION) && JRUBY_VERSION =~ /1\.6\.5.*/
|
4
|
+
raise "Fibers are broken in JRuby 1.6.5 (See JRUBY-6170)"
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'strand'
|
8
|
+
|
9
|
+
module ZooKeeper
|
10
|
+
module EventMachine
|
11
|
+
|
12
|
+
class ClientConn < ::EM::Connection
|
13
|
+
include Protocol
|
14
|
+
include Slf4r::Logger
|
15
|
+
|
16
|
+
unless EventMachine.methods.include?(:set_pending_connect_timeout)
|
17
|
+
def set_pending_connect_timeout(timeout)
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(session,connect_timeout)
|
23
|
+
@session = session
|
24
|
+
@connect_timeout = connect_timeout
|
25
|
+
set_pending_connect_timeout(connect_timeout)
|
26
|
+
rescue Exception => ex
|
27
|
+
logger.warn("Exception in initialize",ex)
|
28
|
+
end
|
29
|
+
|
30
|
+
def post_init()
|
31
|
+
rescue Exception => ex
|
32
|
+
logger.warn("Exception in post_init",ex)
|
33
|
+
end
|
34
|
+
|
35
|
+
def connection_completed()
|
36
|
+
@session.prime_connection(self)
|
37
|
+
|
38
|
+
# Make sure we connect within the timeout period
|
39
|
+
# TODO this should really be the amount of connect timeout left over
|
40
|
+
@timer = EM.add_timer(@connect_timeout) do
|
41
|
+
if @session.connected?
|
42
|
+
# Start the ping timer
|
43
|
+
ping = @session.ping_interval
|
44
|
+
@timer = EM.add_periodic_timer ( ping ) do
|
45
|
+
case @ping
|
46
|
+
when 1 then @session.ping()
|
47
|
+
when 2 then close_connection()
|
48
|
+
end
|
49
|
+
@ping += 1
|
50
|
+
end
|
51
|
+
else
|
52
|
+
close_connection()
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
rescue Exception => ex
|
57
|
+
logger.warn("Exception in connection_completed",ex)
|
58
|
+
end
|
59
|
+
|
60
|
+
def receive_records(packet_io)
|
61
|
+
@ping = 0
|
62
|
+
@session.receive_records(packet_io)
|
63
|
+
end
|
64
|
+
|
65
|
+
def disconnect()
|
66
|
+
close_connection()
|
67
|
+
end
|
68
|
+
|
69
|
+
def unbind
|
70
|
+
EM.cancel_timer(@timer) if @timer
|
71
|
+
@session.disconnected()
|
72
|
+
rescue Exception => ex
|
73
|
+
logger.warn("Exception in unbind",ex)
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
# The EventMachine binding is very simple because there is only one thread!
|
80
|
+
# and we have good stuff like timers provided for us
|
81
|
+
class Binding
|
82
|
+
include Slf4r::Logger
|
83
|
+
# We can use this binding if we are running in the reactor thread
|
84
|
+
def self.available?()
|
85
|
+
EM.reactor_running? && EM.reactor_thread?
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.context(&context_block)
|
89
|
+
s = Strand.new() do
|
90
|
+
context_block.call(Strand)
|
91
|
+
end
|
92
|
+
s.join
|
93
|
+
end
|
94
|
+
|
95
|
+
attr_reader :client, :session
|
96
|
+
def start(client,session)
|
97
|
+
@client = client
|
98
|
+
@session = session
|
99
|
+
@session.start()
|
100
|
+
end
|
101
|
+
|
102
|
+
def connect(host,port,delay,timeout)
|
103
|
+
EM.add_timer(delay) do
|
104
|
+
EM.connect(host,port,ZooKeeper::EventMachine::ClientConn,@session,timeout)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# You are working in event machine it is up to you to ensure your callbacks do not block
|
109
|
+
def invoke(callback,*args)
|
110
|
+
callback.call(*args)
|
111
|
+
end
|
112
|
+
|
113
|
+
def queue_request(*args,&callback)
|
114
|
+
op = AsyncOp.new(self,&callback)
|
115
|
+
begin
|
116
|
+
@session.queue_request(*args) do |error,response|
|
117
|
+
op.resume(error,response)
|
118
|
+
end
|
119
|
+
rescue ZooKeeper::Error => ex
|
120
|
+
op.resume(ex,nil)
|
121
|
+
end
|
122
|
+
|
123
|
+
op
|
124
|
+
end
|
125
|
+
|
126
|
+
def close(&callback)
|
127
|
+
|
128
|
+
op = AsyncOp.new(self,&callback)
|
129
|
+
|
130
|
+
begin
|
131
|
+
@session.close() do |error,response|
|
132
|
+
op.resume(error,response)
|
133
|
+
end
|
134
|
+
rescue ZooKeeper::Error => ex
|
135
|
+
op.resume(ex,nil)
|
136
|
+
end
|
137
|
+
|
138
|
+
op
|
139
|
+
end
|
140
|
+
|
141
|
+
end #class Binding
|
142
|
+
|
143
|
+
class AsyncOp < ZooKeeper::AsyncOp
|
144
|
+
|
145
|
+
def initialize(binding,&callback)
|
146
|
+
@em_binding = binding
|
147
|
+
|
148
|
+
# Wrap the callback in its own Strand
|
149
|
+
@op_strand = Strand.new do
|
150
|
+
#immediately pause
|
151
|
+
error, response = Strand.yield
|
152
|
+
Strand.current[ZooKeeper::CURRENT] = [ @em_binding.client ]
|
153
|
+
raise error if error
|
154
|
+
callback.call(response)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def resume(error,response)
|
159
|
+
#TODO - raise issue in strand for resume to take arguments
|
160
|
+
op_strand.fiber.resume(error,response)
|
161
|
+
end
|
162
|
+
|
163
|
+
private
|
164
|
+
|
165
|
+
attr_reader :op_strand,:err_strand
|
166
|
+
|
167
|
+
def set_error_handler(errcallback)
|
168
|
+
@err_strand = Strand.new() do
|
169
|
+
begin
|
170
|
+
op_strand.value()
|
171
|
+
rescue StandardError => ex
|
172
|
+
Strand.current[ZooKeeper::CURRENT] = [ @em_binding.client ]
|
173
|
+
errcallback.call(ex)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def wait_value()
|
179
|
+
err_strand ? err_strand.value : op_strand.value
|
180
|
+
end
|
181
|
+
|
182
|
+
end #class AsyncOp
|
183
|
+
end #module EventMachine
|
184
|
+
end #module ZooKeeper
|
185
|
+
|
186
|
+
ZooKeeper.add_binding(ZooKeeper::EventMachine::Binding)
|
data/lib/zkruby/multi.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
module ZooKeeper
|
2
|
+
|
3
|
+
module Proto
|
4
|
+
|
5
|
+
#MultiTransactionRecord and MultiResponse in java
|
6
|
+
#are not able to be compiled from Jute because jute doesn't support
|
7
|
+
#discriminators
|
8
|
+
class MultiRequest < BinData::Record
|
9
|
+
array :requests, :read_until => lambda { element.header._type < 0 } do
|
10
|
+
multi_header :header
|
11
|
+
choice :request, :selection => lambda { header._type },
|
12
|
+
:onlyif => lambda { header._type > 0 } do
|
13
|
+
create_request 1
|
14
|
+
delete_request 2
|
15
|
+
set_data_request 5
|
16
|
+
check_version_request 13
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
class MultiResponseItem < BinData::Record
|
23
|
+
multi_header :header
|
24
|
+
choice :response, :selection => lambda { header._type },
|
25
|
+
:onlyif => lambda { has_response? } do
|
26
|
+
error_response -1
|
27
|
+
create_response 1
|
28
|
+
set_data_response 5
|
29
|
+
end
|
30
|
+
|
31
|
+
def has_response?
|
32
|
+
!done? && [ -1, 1, 5 ] .include?(header._type)
|
33
|
+
end
|
34
|
+
|
35
|
+
def done?
|
36
|
+
# Boolean's don't actually work properly in bindata
|
37
|
+
header.done == true
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
class MultiResponse < BinData::Record
|
43
|
+
array :responses, :type => :multi_response_item, :read_until => lambda { element.done? }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
@@ -0,0 +1,182 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
|
3
|
+
module ZooKeeper
|
4
|
+
# Represents a problem communicating with the ZK cluster
|
5
|
+
# client's shouldn't see this
|
6
|
+
class ProtocolError < IOError;end
|
7
|
+
|
8
|
+
# Raw object protocol, very similar to EM's native ObjectProtocol
|
9
|
+
# Expects:
|
10
|
+
# #receive_data and #send_records to be invoked
|
11
|
+
# #receive_records and #send_data to be implemented
|
12
|
+
module Protocol
|
13
|
+
MIN_PACKET = 5 #TODO Work out what the min packet size really is
|
14
|
+
include Slf4r::Logger
|
15
|
+
|
16
|
+
def receive_data data # :nodoc:
|
17
|
+
|
18
|
+
@buffer ||= StringIO.new()
|
19
|
+
@buffer.seek(0, IO::SEEK_END)
|
20
|
+
@buffer << data
|
21
|
+
@buffer.rewind
|
22
|
+
logger.debug { "Received #{data.length} bytes: buffer length = #{@buffer.length} pos = #{@buffer.pos}" }
|
23
|
+
loop do
|
24
|
+
if @buffer.length - @buffer.pos > MIN_PACKET
|
25
|
+
packet_size = @buffer.read(4).unpack("N").first
|
26
|
+
if (@buffer.length - @buffer.pos >= packet_size)
|
27
|
+
expected_pos = @buffer.pos + packet_size
|
28
|
+
# We just pass the buffer around and expect packet_size to be consumed
|
29
|
+
receive_records(@buffer)
|
30
|
+
if (@buffer.pos != expected_pos)
|
31
|
+
#this can happen during disconnection with left over packets
|
32
|
+
#the connection is dying anyway
|
33
|
+
leftover = @buffer.read(packet_size).unpack("H*")[0]
|
34
|
+
raise ProtocolError, "Records not consumed #{leftover}"
|
35
|
+
end
|
36
|
+
logger.debug { "Consumed packet #{packet_size}. Buffer pos=#{@buffer.pos}, length=#{@buffer.length}" }
|
37
|
+
next
|
38
|
+
else
|
39
|
+
# found the last partial packet
|
40
|
+
@buffer.seek(-4, IO::SEEK_CUR)
|
41
|
+
logger.debug { "Buffer contains #{@buffer.length} of #{packet_size} packet" }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
break
|
45
|
+
end
|
46
|
+
# reset the buffer
|
47
|
+
@buffer = StringIO.new(@buffer.read()) if @buffer.pos > 0
|
48
|
+
end
|
49
|
+
|
50
|
+
def receive_records(packet_io)
|
51
|
+
#stub
|
52
|
+
#we don't unpack records here because we don't know what kind of records they are!
|
53
|
+
end
|
54
|
+
|
55
|
+
def send_records(*records)
|
56
|
+
length = 0
|
57
|
+
bindata = records.collect { |r| s = r.to_binary_s; length += s.length; s }
|
58
|
+
send_data([length].pack("N"))
|
59
|
+
bindata.each { |b| send_data(b) }
|
60
|
+
logger.debug { "Sent #{length} byte packet containing #{records.length} records" }
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class Operation
|
65
|
+
attr_reader :op, :opcode, :request, :response, :callback
|
66
|
+
def initialize(op,opcode,request,response,callback)
|
67
|
+
@op=op;@opcode=opcode
|
68
|
+
@request=request;@response=response
|
69
|
+
@callback=callback
|
70
|
+
end
|
71
|
+
|
72
|
+
def path
|
73
|
+
#Every request has a path!
|
74
|
+
#TODO - path may be chrooted!
|
75
|
+
request.path if request.respond_to?(:path)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
class Packet < Operation
|
80
|
+
attr_reader :xid, :watch_type, :watcher
|
81
|
+
|
82
|
+
def initialize(xid,op,opcode,request,response,watch_type,watcher,callback)
|
83
|
+
super(op,opcode,request,response,callback)
|
84
|
+
@xid=xid;
|
85
|
+
@watch_type = watch_type; @watcher = watcher
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
def error(reason)
|
90
|
+
result(reason)[0..2] # don't need the watch
|
91
|
+
end
|
92
|
+
|
93
|
+
def result(rc)
|
94
|
+
error = nil
|
95
|
+
unless (Error::NONE === rc) then
|
96
|
+
error = Error.lookup(rc)
|
97
|
+
error = error.exception("ZooKeeper error for #{@op}(#{path}) ")
|
98
|
+
end
|
99
|
+
[ callback, error ,response, watch_type ]
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# NoNode error is expected for exists
|
104
|
+
class ExistsPacket < Packet
|
105
|
+
def result(rc)
|
106
|
+
Error::NO_NODE === rc ? [ callback, nil, nil, :exists ] : super(rc)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# In the normal case the close packet will be last and will get
|
111
|
+
# cleared via disconnected() and :session_expired
|
112
|
+
class ClosePacket < Packet
|
113
|
+
def result(rc)
|
114
|
+
Error::SESSION_EXPIRED == rc ? [ callback, nil, nil, nil ] : super(rc)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
|
119
|
+
# Returned by asynchronous calls
|
120
|
+
#
|
121
|
+
# @example
|
122
|
+
# op = zk.stat("\apath") { |stat| something_with_stat() }
|
123
|
+
#
|
124
|
+
# op.on_error do |err|
|
125
|
+
# case err
|
126
|
+
# when ZK::Error::SESSION_EXPIRED
|
127
|
+
# puts "Ignoring session expired"
|
128
|
+
# else
|
129
|
+
# raise err
|
130
|
+
# end
|
131
|
+
# end
|
132
|
+
#
|
133
|
+
# begin
|
134
|
+
# result_of_somthing_with_stat = op.value
|
135
|
+
# rescue StandardError => ex
|
136
|
+
# puts "Oops, my error handler raised an exception"
|
137
|
+
# end
|
138
|
+
#
|
139
|
+
#
|
140
|
+
class AsyncOp
|
141
|
+
|
142
|
+
attr_accessor :backtrace
|
143
|
+
|
144
|
+
# Provide an error callback.
|
145
|
+
# @param block the error callback as a block
|
146
|
+
# @yieldparam [StandardError] the error raised by the zookeeper operation OR by its callback
|
147
|
+
def errback(&block)
|
148
|
+
set_error_handler(block)
|
149
|
+
end
|
150
|
+
|
151
|
+
# @param block the error callback as a Proc
|
152
|
+
def errback=(block)
|
153
|
+
set_error_handler(block)
|
154
|
+
end
|
155
|
+
|
156
|
+
alias :on_error :errback
|
157
|
+
|
158
|
+
# Wait for the async op to finish and returns its value
|
159
|
+
# will raise an exception if
|
160
|
+
# the operation itself fails and there is no error handler
|
161
|
+
# the callback raises a StandardError and there is no error handler
|
162
|
+
# the error handler raises StandardError
|
163
|
+
def value();
|
164
|
+
wait_value()
|
165
|
+
rescue ZooKeeper::Error => ex
|
166
|
+
# Set the backtrace to the original caller, rather than the ZK event loop
|
167
|
+
ex.set_backtrace(@backtrace) if @backtrace
|
168
|
+
raise ex
|
169
|
+
end
|
170
|
+
|
171
|
+
private
|
172
|
+
|
173
|
+
def set_error_handler(blk)
|
174
|
+
raise NotImplementedError, ":wait_result to be privately implemented by binding"
|
175
|
+
end
|
176
|
+
|
177
|
+
def wait_value();
|
178
|
+
raise NotImplementedError, ":wait_result to be privately implemented by binding"
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|