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/util.rb
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
module ZooKeeper
|
2
|
+
|
3
|
+
class Client
|
4
|
+
|
5
|
+
# Recursive make path
|
6
|
+
#
|
7
|
+
# This will send parallel creates for ALL nodes up to the root
|
8
|
+
# and then ignore any NODE_EXISTS errors.
|
9
|
+
#
|
10
|
+
# You generally want to call this after receiving a NO_NODE error from a
|
11
|
+
# simple {#create}
|
12
|
+
#
|
13
|
+
# @param [String] path
|
14
|
+
# @param [Data::ACL] acl the access control list to apply to any created nodes
|
15
|
+
# @return
|
16
|
+
# @raise [Error]
|
17
|
+
def mkpath(path,acl=ZK::ACL_OPEN_UNSAFE,&callback)
|
18
|
+
|
19
|
+
return synchronous_call(:mkpath,path,acl) unless block_given?
|
20
|
+
|
21
|
+
connection_lost = false
|
22
|
+
|
23
|
+
path_comp = path.split("/")
|
24
|
+
|
25
|
+
# we only care about the result of the final create op
|
26
|
+
last_op = nil
|
27
|
+
|
28
|
+
(1-path_comp.length..-1).each do |i|
|
29
|
+
|
30
|
+
sub_path = path_comp[0..i].join("/")
|
31
|
+
|
32
|
+
op = create(sub_path,"",acl) { if i == -1 then callback.call() else :true end }
|
33
|
+
|
34
|
+
op.errback do |err|
|
35
|
+
if i == -1
|
36
|
+
if ZK::Error::NODE_EXISTS === err
|
37
|
+
callback.call()
|
38
|
+
elsif ZK::Error::CONNECTION_LOST === err || ( ZK::Error::NO_NODE && connection_lost )
|
39
|
+
# try again
|
40
|
+
mkpath(path,acl)
|
41
|
+
callback.call()
|
42
|
+
else
|
43
|
+
raise err
|
44
|
+
end
|
45
|
+
elsif ZK::Error::CONNECTION_LOST === err
|
46
|
+
connection_lost = true
|
47
|
+
:connection_lost
|
48
|
+
else
|
49
|
+
# we don't care about any other errors, but we will log them
|
50
|
+
logger.warn { "Error creating #{sub_path}, #{err}" }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
last_op = op if (i == -1)
|
54
|
+
end
|
55
|
+
|
56
|
+
return WrappedOp.new(last_op)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Recursive delete
|
60
|
+
#
|
61
|
+
# Although this method itself can be called synchronously all the zk activity
|
62
|
+
# is asynchronous, ie all subnodes are removed in parallel
|
63
|
+
#
|
64
|
+
# Will retry on connection loss, or if some other activity is adding nodes in parallel.
|
65
|
+
# If you get a session expiry in the middle of this you will end up with a
|
66
|
+
# partially completed recursive delete. In all other circumstances it will eventually
|
67
|
+
# complete.
|
68
|
+
#
|
69
|
+
# @overload rmpath(path,version)
|
70
|
+
# @param [String] path this node and all its children will be recursively deleted
|
71
|
+
# @param [FixNum] version the expected version to be deleted (-1 to match any version).
|
72
|
+
# Only applies to the top level path
|
73
|
+
# @return
|
74
|
+
# @raise [Error]
|
75
|
+
# @overload rmpath(path,version)
|
76
|
+
# @return [AsyncOp]
|
77
|
+
# @yield [] callback invoked if delete is successful
|
78
|
+
def rmpath(path,version = -1, &callback)
|
79
|
+
|
80
|
+
return synchronous_call(:rmpath,path,version) unless block_given?
|
81
|
+
|
82
|
+
del_op = delete(path,version) { callback.call() }
|
83
|
+
|
84
|
+
del_op.errback do |err|
|
85
|
+
# we don't leave this method unless we get an exception
|
86
|
+
# or have completed and called the callback
|
87
|
+
case err
|
88
|
+
when ZK::Error::NO_NODE
|
89
|
+
when ZK::Error::CONNECTION_LOST
|
90
|
+
rmpath(path,version)
|
91
|
+
when ZK::Error::NOT_EMPTY
|
92
|
+
|
93
|
+
stat, child_list = children(path)
|
94
|
+
|
95
|
+
unless child_list.empty?
|
96
|
+
child_ops = {}
|
97
|
+
child_list.each do |child|
|
98
|
+
child_path = "#{path}/#{child}"
|
99
|
+
|
100
|
+
rm_op = rmpath(child_path,-1) { :success }
|
101
|
+
|
102
|
+
rm_op.errback { |err| child_results[child_path] = err }
|
103
|
+
|
104
|
+
child_ops[child_path] = rm_op
|
105
|
+
end
|
106
|
+
|
107
|
+
# Wait until all our children are done (or error'd)
|
108
|
+
child_ops.each { |child_path,op| op.value }
|
109
|
+
rmpath(path,version)
|
110
|
+
end
|
111
|
+
else
|
112
|
+
raise err
|
113
|
+
end
|
114
|
+
callback.call()
|
115
|
+
end
|
116
|
+
|
117
|
+
return WrappedOp.new(del_op)
|
118
|
+
end
|
119
|
+
end #Client
|
120
|
+
|
121
|
+
class WrappedOp < AsyncOp
|
122
|
+
def initialize(delegate_op)
|
123
|
+
@delegate_op = delegate_op
|
124
|
+
@errback = nil
|
125
|
+
end
|
126
|
+
|
127
|
+
private
|
128
|
+
def wait_value()
|
129
|
+
begin
|
130
|
+
@delegate_op.value()
|
131
|
+
rescue StandardError > err
|
132
|
+
@errback ? @errback.call(err) : raise
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def set_error_handler(errback)
|
137
|
+
@errback = errback
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
end #ZooKeeper
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# A pure ruby implementation of the zk client library
|
2
|
+
#
|
3
|
+
# It implements the client side of the ZooKeeper TCP protocol directly rather
|
4
|
+
# than calling the zk client libraries
|
5
|
+
#
|
6
|
+
module ZooKeeper
|
7
|
+
# Major/Minor numbers track zookeeper itself, final digit is our build number
|
8
|
+
VERSION = "3.4.3"
|
9
|
+
@bindings = []
|
10
|
+
def self.add_binding(binding)
|
11
|
+
@bindings << binding unless @bindings.include?(binding)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# Shorthand
|
16
|
+
ZK=ZooKeeper
|
17
|
+
|
18
|
+
require 'slf4r'
|
19
|
+
require 'zkruby/enum'
|
20
|
+
require 'zkruby/bindata'
|
21
|
+
require 'jute/zookeeper'
|
22
|
+
require 'zkruby/multi'
|
23
|
+
require 'zkruby/protocol'
|
24
|
+
require 'zkruby/session'
|
25
|
+
require 'zkruby/client'
|
26
|
+
# Utilities
|
27
|
+
require 'zkruby/util'
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class ZKBTest < BinData::Record
|
4
|
+
int8 :testint
|
5
|
+
zk_boolean :bool
|
6
|
+
end
|
7
|
+
|
8
|
+
describe ZK::ZKBoolean do
|
9
|
+
|
10
|
+
it "should behave like 'false' after reading 00" do
|
11
|
+
hex = "0000"
|
12
|
+
bin = [ hex ].pack("H*")
|
13
|
+
|
14
|
+
b = ZKBTest.read(bin)
|
15
|
+
|
16
|
+
b.bool.should == false
|
17
|
+
# NOTE!! this is unfortunate consequence of Ruby's view of FALSE
|
18
|
+
b.bool.should_not be_false
|
19
|
+
|
20
|
+
b.to_binary_s.unpack("H*")[0].should == hex
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should behave like 'true' after reading 01" do
|
24
|
+
hex = "0001"
|
25
|
+
bin = [ hex ].pack("H*")
|
26
|
+
|
27
|
+
b = ZKBTest.read(bin)
|
28
|
+
|
29
|
+
b.bool.should == true
|
30
|
+
# NOTE!! this is unfortunate consequence of Ruby's view of FALSE
|
31
|
+
b.bool.should be_true
|
32
|
+
|
33
|
+
b.to_binary_s.unpack("H*")[0].should == hex
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should behave like a boolean after creating" do
|
37
|
+
|
38
|
+
b = ZKBTest.new()
|
39
|
+
b.bool= true
|
40
|
+
b.bool.should == true
|
41
|
+
|
42
|
+
b.bool= false
|
43
|
+
b.bool.should == false
|
44
|
+
|
45
|
+
b = ZKBTest.new( :bool => true )
|
46
|
+
b.bool.should == true
|
47
|
+
|
48
|
+
b = ZKBTest.new( :bool => false )
|
49
|
+
b.bool.should == false
|
50
|
+
end
|
51
|
+
end
|
data/spec/enum_spec.rb
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'zkruby/enum.rb'
|
2
|
+
|
3
|
+
class TestEnum
|
4
|
+
include Enumeration
|
5
|
+
|
6
|
+
enum :one, 1
|
7
|
+
enum :two, -2
|
8
|
+
end
|
9
|
+
|
10
|
+
class TestError < StandardError
|
11
|
+
include Enumeration
|
12
|
+
enum :badone, 10
|
13
|
+
enum :oops,20
|
14
|
+
end
|
15
|
+
|
16
|
+
describe Enumeration do
|
17
|
+
it "should equate to itself" do
|
18
|
+
TestEnum::ONE.should == TestEnum::ONE
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should have case equals comparisons to symbols and ints" do
|
22
|
+
t = TestEnum.fetch(1)
|
23
|
+
t.should === :one
|
24
|
+
t.should === 1
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should be useable in a mock" do
|
28
|
+
m = mock("watchevent")
|
29
|
+
m.should_receive(:test).with(TestEnum::TWO)
|
30
|
+
m.test(TestEnum::TWO)
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "errors" do
|
34
|
+
# Ruby 1.9 inadvertently dropped requirement that the argument
|
35
|
+
# to rescue must be a class or module, but despite its usefulness
|
36
|
+
# it is not supposed to be that way and JRuby doesn't work that way
|
37
|
+
it "should be an exception class" do
|
38
|
+
TestError.get(:badone).should equal (TestError::BADONE)
|
39
|
+
TestError.get(:badone).class.should == Class
|
40
|
+
TestError::BADONE.should < StandardError
|
41
|
+
TestError::BADONE.new().should be_kind_of(TestError)
|
42
|
+
TestError::OOPS.should === TestError.get(:oops).exception()
|
43
|
+
TestError::OOPS.to_sym.should == :oops
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should be raisable with get and rescuable by a constant" do
|
47
|
+
begin
|
48
|
+
raise TestError.get(:badone), "mymessage"
|
49
|
+
rescue TestError::BADONE => ex
|
50
|
+
ex.message.should == "mymessage"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should be raisable with constant and rescuable with fetch" do
|
55
|
+
begin
|
56
|
+
raise TestError::OOPS, "a message"
|
57
|
+
rescue TestError::fetch(20) => ex
|
58
|
+
# do nothing
|
59
|
+
ex.message.should == "a message"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should catch the superclass" do
|
64
|
+
begin
|
65
|
+
raise TestError::BADONE, "a message"
|
66
|
+
rescue TestError
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should be raisable with unknown symbol" do
|
71
|
+
begin
|
72
|
+
raise TestError.lookup(:unknown)
|
73
|
+
rescue TestError => err
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should be raisable with unknown int" do
|
78
|
+
begin
|
79
|
+
raise TestError.lookup(-132)
|
80
|
+
rescue TestError => err
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'server_helper'
|
2
|
+
require 'shared/binding'
|
3
|
+
require 'zkruby/eventmachine'
|
4
|
+
|
5
|
+
module EMHelper
|
6
|
+
alias :restart_cluster_orig :restart_cluster
|
7
|
+
def restart_cluster(delay=0)
|
8
|
+
if EM.reactor_running?
|
9
|
+
cv = Strand::ConditionVariable.new()
|
10
|
+
op = Proc.new do
|
11
|
+
begin
|
12
|
+
restart_cluster_orig(delay)
|
13
|
+
rescue Exception => ex
|
14
|
+
logger.error ("Exception restarting cluster #{ex}")
|
15
|
+
end
|
16
|
+
true
|
17
|
+
end
|
18
|
+
cb = Proc.new { |result| cv.signal() }
|
19
|
+
defer = EM.defer(op,cb)
|
20
|
+
cv.wait()
|
21
|
+
else
|
22
|
+
restart_cluster_orig(delay)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def sleep(delay)
|
27
|
+
Strand.sleep(delay)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe ZooKeeper::EventMachine::Binding do
|
32
|
+
|
33
|
+
include Slf4r::Logger
|
34
|
+
include EMHelper
|
35
|
+
|
36
|
+
around(:each) do |example|
|
37
|
+
EventMachine.run {
|
38
|
+
Strand.new() do
|
39
|
+
begin
|
40
|
+
example.run
|
41
|
+
ensure
|
42
|
+
EM::stop
|
43
|
+
end
|
44
|
+
end
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
it_should_behave_like "a zookeeper client binding"
|
49
|
+
|
50
|
+
end
|
data/spec/multi_spec.rb
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
def tohex(s)
|
4
|
+
s.unpack("H*")[0]
|
5
|
+
end
|
6
|
+
|
7
|
+
describe ZooKeeper::Proto::MultiRequest do
|
8
|
+
|
9
|
+
it "an empty multi should match the java output" do
|
10
|
+
|
11
|
+
java_hex = "ffffffff01ffffffff"
|
12
|
+
java_bin = [ java_hex ].pack("H*")
|
13
|
+
|
14
|
+
read_request = ZooKeeper::Proto::MultiRequest.read(java_bin)
|
15
|
+
|
16
|
+
read_hex = tohex(read_request.to_binary_s)
|
17
|
+
|
18
|
+
m = ZooKeeper::Proto::MultiRequest.new()
|
19
|
+
|
20
|
+
m.requests << { :header => { :_type => -1, :done => true, :err => -1 } }
|
21
|
+
|
22
|
+
m_hex = tohex(m.to_binary_s)
|
23
|
+
m_hex.should == java_hex
|
24
|
+
m.should == read_request
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should match the java output with records" do
|
28
|
+
|
29
|
+
java_hex = "0000000d00ffffffff00000005636865636b000000010000000100ffffffff000000066372656174650000000b6372656174652064617461000000010000001f000000046175746800000000000000000000000200ffffffff0000000664656c657465000000110000000500ffffffff000000077365744461746100000008736574206461746100000013ffffffff01ffffffff"
|
30
|
+
|
31
|
+
java_bin = [ java_hex ].pack("H*")
|
32
|
+
|
33
|
+
read_request = ZooKeeper::Proto::MultiRequest.read(java_bin)
|
34
|
+
|
35
|
+
read_hex = tohex(read_request.to_binary_s)
|
36
|
+
|
37
|
+
read_hex.should == java_hex
|
38
|
+
|
39
|
+
m = ZooKeeper::Proto::MultiRequest.new()
|
40
|
+
|
41
|
+
check = ZK::Proto::CheckVersionRequest.new( :path => "check", :version => 1 )
|
42
|
+
m.requests << { :header => { :_type => 13, :done => false, :err => -1 }, :request => check }
|
43
|
+
|
44
|
+
create = ZK::Proto::CreateRequest.new( :path => "create", :data => "create data", :acl => ZK::CREATOR_ALL_ACL, :flags => 0)
|
45
|
+
m.requests << { :header => { :_type => 1, :done => false, :err => -1 }, :request => create }
|
46
|
+
|
47
|
+
delete = ZK::Proto::DeleteRequest.new( :path => "delete", :version => 17)
|
48
|
+
m.requests << { :header => { :_type => 2, :done => false, :err => -1 }, :request => delete }
|
49
|
+
|
50
|
+
|
51
|
+
set = ZK::Proto::SetDataRequest.new( :path => "setData", :data => "set data", :version => 19)
|
52
|
+
m.requests << { :header => { :_type => 5, :done => false, :err => -1 }, :request => set }
|
53
|
+
|
54
|
+
m.requests << { :header => { :_type => -1, :done => true, :err => -1 } }
|
55
|
+
|
56
|
+
m_hex = tohex(m.to_binary_s)
|
57
|
+
m_hex.should == java_hex
|
58
|
+
m.should == read_request
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should decode a response" do
|
62
|
+
|
63
|
+
response_hex = "000000010000000000000000062f6d756c7469ffffffff01ffffffff"
|
64
|
+
|
65
|
+
response_bin = [ response_hex ].pack("H*")
|
66
|
+
|
67
|
+
m = ZooKeeper::Proto::MultiResponse.read(response_bin)
|
68
|
+
m.responses.size.should == 2
|
69
|
+
m.responses[0].done?.should be_false
|
70
|
+
m.responses[0].header.err.should == 0
|
71
|
+
m.responses[0].header._type.should == 1
|
72
|
+
m.responses[0].response.path.should == "/multi"
|
73
|
+
m.responses[1].done?.should be_true
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should decode an error response" do
|
78
|
+
response_hex = "ffffffff000000000000000000ffffffff00ffffff9bffffff9bffffffff01ffffffff"
|
79
|
+
|
80
|
+
response_bin = [ response_hex ].pack("H*")
|
81
|
+
|
82
|
+
m = ZooKeeper::Proto::MultiResponse.read(response_bin)
|
83
|
+
m.responses.size.should == 3
|
84
|
+
m.responses[0].header._type.should == -1
|
85
|
+
m.responses[0].header.err.should == 0
|
86
|
+
m.responses[0].done?.should be_false
|
87
|
+
m.responses[0].response.err.should == 0
|
88
|
+
m.responses[1].header._type.should == -1
|
89
|
+
m.responses[1].header.err.should == -101
|
90
|
+
m.responses[1].response.err.should == -101
|
91
|
+
m.responses[2].done?.should be_true
|
92
|
+
end
|
93
|
+
end
|