zkruby 3.4.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|