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/em_zkruby.rb
ADDED
@@ -0,0 +1,203 @@
|
|
1
|
+
module ZooKeeper::Data
|
2
|
+
class Identity < BinData::Record
|
3
|
+
zk_string :scheme
|
4
|
+
zk_string :identity
|
5
|
+
end
|
6
|
+
class ACL < BinData::Record
|
7
|
+
int32be :perms
|
8
|
+
identity :identity
|
9
|
+
end
|
10
|
+
class Stat < BinData::Record
|
11
|
+
int64be :czxid
|
12
|
+
int64be :mzxid
|
13
|
+
int64be :ctime
|
14
|
+
int64be :mtime
|
15
|
+
int32be :version
|
16
|
+
int32be :cversion
|
17
|
+
int32be :aversion
|
18
|
+
int64be :ephemeral_owner
|
19
|
+
int32be :data_length
|
20
|
+
int32be :num_children
|
21
|
+
int64be :pzxid
|
22
|
+
end
|
23
|
+
class StatPersisted < BinData::Record
|
24
|
+
int64be :czxid
|
25
|
+
int64be :mzxid
|
26
|
+
int64be :ctime
|
27
|
+
int64be :mtime
|
28
|
+
int32be :version
|
29
|
+
int32be :cversion
|
30
|
+
int32be :aversion
|
31
|
+
int64be :ephemeral_owner
|
32
|
+
int64be :pzxid
|
33
|
+
end
|
34
|
+
class StatPersistedV1 < BinData::Record
|
35
|
+
int64be :czxid
|
36
|
+
int64be :mzxid
|
37
|
+
int64be :ctime
|
38
|
+
int64be :mtime
|
39
|
+
int32be :version
|
40
|
+
int32be :cversion
|
41
|
+
int32be :aversion
|
42
|
+
int64be :ephemeral_owner
|
43
|
+
end
|
44
|
+
end
|
45
|
+
module ZooKeeper::Proto
|
46
|
+
class ConnectRequest < BinData::Record
|
47
|
+
int32be :protocol_version
|
48
|
+
int64be :last_zxid_seen
|
49
|
+
int32be :time_out
|
50
|
+
int64be :session_id
|
51
|
+
zk_buffer :passwd
|
52
|
+
end
|
53
|
+
class ConnectResponse < BinData::Record
|
54
|
+
int32be :protocol_version
|
55
|
+
int32be :time_out
|
56
|
+
int64be :session_id
|
57
|
+
zk_buffer :passwd
|
58
|
+
end
|
59
|
+
class SetWatches < BinData::Record
|
60
|
+
int64be :relative_zxid
|
61
|
+
hide :data_watches__length
|
62
|
+
int32be :data_watches__length, :value => lambda { data_watches.length }
|
63
|
+
array :data_watches, :type => :zk_string, :initial_length => :data_watches__length
|
64
|
+
hide :exist_watches__length
|
65
|
+
int32be :exist_watches__length, :value => lambda { exist_watches.length }
|
66
|
+
array :exist_watches, :type => :zk_string, :initial_length => :exist_watches__length
|
67
|
+
hide :child_watches__length
|
68
|
+
int32be :child_watches__length, :value => lambda { child_watches.length }
|
69
|
+
array :child_watches, :type => :zk_string, :initial_length => :child_watches__length
|
70
|
+
end
|
71
|
+
class RequestHeader < BinData::Record
|
72
|
+
int32be :xid
|
73
|
+
int32be :_type
|
74
|
+
end
|
75
|
+
class MultiHeader < BinData::Record
|
76
|
+
int32be :_type
|
77
|
+
zk_boolean :done
|
78
|
+
int32be :err
|
79
|
+
end
|
80
|
+
class AuthPacket < BinData::Record
|
81
|
+
int32be :_type
|
82
|
+
zk_string :scheme
|
83
|
+
zk_buffer :auth
|
84
|
+
end
|
85
|
+
class ReplyHeader < BinData::Record
|
86
|
+
int32be :xid
|
87
|
+
int64be :zxid
|
88
|
+
int32be :err
|
89
|
+
end
|
90
|
+
class GetDataRequest < BinData::Record
|
91
|
+
zk_string :path
|
92
|
+
zk_boolean :watch
|
93
|
+
end
|
94
|
+
class SetDataRequest < BinData::Record
|
95
|
+
zk_string :path
|
96
|
+
zk_buffer :data
|
97
|
+
int32be :version
|
98
|
+
end
|
99
|
+
class SetDataResponse < BinData::Record
|
100
|
+
stat :stat
|
101
|
+
end
|
102
|
+
class GetSASLRequest < BinData::Record
|
103
|
+
zk_buffer :token
|
104
|
+
end
|
105
|
+
class SetSASLRequest < BinData::Record
|
106
|
+
zk_buffer :token
|
107
|
+
end
|
108
|
+
class SetSASLResponse < BinData::Record
|
109
|
+
zk_buffer :token
|
110
|
+
end
|
111
|
+
class CreateRequest < BinData::Record
|
112
|
+
zk_string :path
|
113
|
+
zk_buffer :data
|
114
|
+
hide :acl__length
|
115
|
+
int32be :acl__length, :value => lambda { acl.length }
|
116
|
+
array :acl, :type => :acl, :initial_length => :acl__length
|
117
|
+
int32be :flags
|
118
|
+
end
|
119
|
+
class DeleteRequest < BinData::Record
|
120
|
+
zk_string :path
|
121
|
+
int32be :version
|
122
|
+
end
|
123
|
+
class GetChildrenRequest < BinData::Record
|
124
|
+
zk_string :path
|
125
|
+
zk_boolean :watch
|
126
|
+
end
|
127
|
+
class GetChildren2Request < BinData::Record
|
128
|
+
zk_string :path
|
129
|
+
zk_boolean :watch
|
130
|
+
end
|
131
|
+
class CheckVersionRequest < BinData::Record
|
132
|
+
zk_string :path
|
133
|
+
int32be :version
|
134
|
+
end
|
135
|
+
class GetMaxChildrenRequest < BinData::Record
|
136
|
+
zk_string :path
|
137
|
+
end
|
138
|
+
class GetMaxChildrenResponse < BinData::Record
|
139
|
+
int32be :maximum
|
140
|
+
end
|
141
|
+
class SetMaxChildrenRequest < BinData::Record
|
142
|
+
zk_string :path
|
143
|
+
int32be :maximum
|
144
|
+
end
|
145
|
+
class SyncRequest < BinData::Record
|
146
|
+
zk_string :path
|
147
|
+
end
|
148
|
+
class SyncResponse < BinData::Record
|
149
|
+
zk_string :path
|
150
|
+
end
|
151
|
+
class GetACLRequest < BinData::Record
|
152
|
+
zk_string :path
|
153
|
+
end
|
154
|
+
class SetACLRequest < BinData::Record
|
155
|
+
zk_string :path
|
156
|
+
hide :acl__length
|
157
|
+
int32be :acl__length, :value => lambda { acl.length }
|
158
|
+
array :acl, :type => :acl, :initial_length => :acl__length
|
159
|
+
int32be :version
|
160
|
+
end
|
161
|
+
class SetACLResponse < BinData::Record
|
162
|
+
stat :stat
|
163
|
+
end
|
164
|
+
class WatcherEvent < BinData::Record
|
165
|
+
int32be :_type
|
166
|
+
int32be :state
|
167
|
+
zk_string :path
|
168
|
+
end
|
169
|
+
class ErrorResponse < BinData::Record
|
170
|
+
int32be :err
|
171
|
+
end
|
172
|
+
class CreateResponse < BinData::Record
|
173
|
+
zk_string :path
|
174
|
+
end
|
175
|
+
class ExistsRequest < BinData::Record
|
176
|
+
zk_string :path
|
177
|
+
zk_boolean :watch
|
178
|
+
end
|
179
|
+
class ExistsResponse < BinData::Record
|
180
|
+
stat :stat
|
181
|
+
end
|
182
|
+
class GetDataResponse < BinData::Record
|
183
|
+
zk_buffer :data
|
184
|
+
stat :stat
|
185
|
+
end
|
186
|
+
class GetChildrenResponse < BinData::Record
|
187
|
+
hide :children__length
|
188
|
+
int32be :children__length, :value => lambda { children.length }
|
189
|
+
array :children, :type => :zk_string, :initial_length => :children__length
|
190
|
+
end
|
191
|
+
class GetChildren2Response < BinData::Record
|
192
|
+
hide :children__length
|
193
|
+
int32be :children__length, :value => lambda { children.length }
|
194
|
+
array :children, :type => :zk_string, :initial_length => :children__length
|
195
|
+
stat :stat
|
196
|
+
end
|
197
|
+
class GetACLResponse < BinData::Record
|
198
|
+
hide :acl__length
|
199
|
+
int32be :acl__length, :value => lambda { acl.length }
|
200
|
+
array :acl, :type => :acl, :initial_length => :acl__length
|
201
|
+
stat :stat
|
202
|
+
end
|
203
|
+
end
|
data/lib/zkruby.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
#TODO This belongs to jute rather than zookeeper and ideally would be a separate project/gem
|
2
|
+
require 'bindata'
|
3
|
+
module ZooKeeper
|
4
|
+
|
5
|
+
class ZKBuffer < BinData::Primitive
|
6
|
+
int32be :len, :value => lambda { data.nil? ? -1 : data.length }
|
7
|
+
string :data, :read_length => :len
|
8
|
+
|
9
|
+
def get; self.data; end
|
10
|
+
def set(v) self.data = v; end
|
11
|
+
end
|
12
|
+
|
13
|
+
class ZKString < BinData::Primitive
|
14
|
+
int32be :len, :value => lambda { data.nil? ? -1 : data.length }
|
15
|
+
string :data, :read_length => :len
|
16
|
+
|
17
|
+
def get; self.data; end
|
18
|
+
def set(v) self.data = v; end
|
19
|
+
def snapshot
|
20
|
+
super.force_encoding('UTF-8')
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
#This doesn't work as expected, because when used in a record
|
25
|
+
# you get a ZKBoolean instance back which cannot be compared to "false"
|
26
|
+
# you must call snapshot or compare with == false
|
27
|
+
class ZKBoolean < BinData::BasePrimitive
|
28
|
+
|
29
|
+
def value_to_binary_string(v)
|
30
|
+
intval = v ? 1 : 0
|
31
|
+
[ intval ].pack("C")
|
32
|
+
end
|
33
|
+
|
34
|
+
def read_and_return_value(io)
|
35
|
+
intval = io.readbytes(1).unpack("C").at(0)
|
36
|
+
intval != 0
|
37
|
+
end
|
38
|
+
|
39
|
+
def sensible_default
|
40
|
+
false
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,608 @@
|
|
1
|
+
module ZooKeeper
|
2
|
+
# Represents failure mode of ZooKeeper operations
|
3
|
+
# They are raised and rescued in the standard ways (much like ruby's Errno)
|
4
|
+
# @example
|
5
|
+
# begin
|
6
|
+
# zk.create(...)
|
7
|
+
# rescue ZK::Error::NODE_EXISTS
|
8
|
+
# ....
|
9
|
+
# rescue ZK::Error::CONNECTION_LOST
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
class Error < StandardError
|
13
|
+
include Enumeration
|
14
|
+
enum :none, 0
|
15
|
+
enum :system_error, (-1)
|
16
|
+
enum :runtime_inconsistency, (-2)
|
17
|
+
enum :data_inconsistency, (-3)
|
18
|
+
enum :connection_lost, (-4)
|
19
|
+
enum :marshalling_error, (-5)
|
20
|
+
enum :unimplemented, (-6)
|
21
|
+
enum :operation_timeout, (-7)
|
22
|
+
enum :bad_arguments, (-8)
|
23
|
+
enum :api_error, (-100)
|
24
|
+
enum :no_node, (-101)
|
25
|
+
enum :no_auth, (-102)
|
26
|
+
enum :bad_version, (-103)
|
27
|
+
enum :no_children_for_ephemerals, (-108)
|
28
|
+
enum :node_exists, (-110)
|
29
|
+
enum :not_empty, (-111)
|
30
|
+
enum :session_expired, (-112)
|
31
|
+
enum :invalid_callback, (-113)
|
32
|
+
enum :invalid_acl, (-114)
|
33
|
+
enum :auth_failed, (-115)
|
34
|
+
enum :session_moved, (-118)
|
35
|
+
enum :unknown, (-999)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Permission constants
|
39
|
+
class Perms
|
40
|
+
include Enumeration
|
41
|
+
enum :read, 1 << 0
|
42
|
+
enum :write, 1 << 1
|
43
|
+
enum :create, 1 << 2
|
44
|
+
enum :delete, 1 << 3
|
45
|
+
enum :admin, 1 << 4
|
46
|
+
enum :all, READ | WRITE | CREATE | DELETE | ADMIN
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
# Combine permissions constants
|
51
|
+
# @param [Perms] perms... list of permissions to combine, can be {Perms} constants, symbols or ints
|
52
|
+
# @return [Fixnum] integer representing the combined permission
|
53
|
+
def self.perms(*perms)
|
54
|
+
perms.inject(0) { | result, perm | result = result | Perms.get(perm) }
|
55
|
+
end
|
56
|
+
|
57
|
+
# Convenience method to create a zk Identity
|
58
|
+
# @param [String] scheme
|
59
|
+
# @param [String] identity
|
60
|
+
# @return [Data::Identity] the encapsulated identity for the given scheme
|
61
|
+
def self.id(scheme,id)
|
62
|
+
Data::Identity.new(:scheme => scheme, :identity => id)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Convenience method to create a zk ACL
|
66
|
+
# ZK.acl(ZK.id("world","anyone"), ZK::Perms.DELETE, ZL::Perms.WRITE)
|
67
|
+
# @param [Data::Identity] id
|
68
|
+
# @param [Perms] *perms list of permissions
|
69
|
+
# @return [Data::ACL] an access control list
|
70
|
+
# @see #perms
|
71
|
+
#
|
72
|
+
#
|
73
|
+
def self.acl(id,*perms)
|
74
|
+
Data::ACL.new( :identity => id, :perms => self.perms(*perms) )
|
75
|
+
end
|
76
|
+
|
77
|
+
#
|
78
|
+
# The Anyone ID
|
79
|
+
ANYONE_ID_UNSAFE = Data::Identity.new(:scheme => "world", :identity => "anyone")
|
80
|
+
|
81
|
+
# Represents the set of auth ids for the current connection
|
82
|
+
AUTH_IDS = Data::Identity.new(:scheme => "auth")
|
83
|
+
|
84
|
+
OPEN_ACL_UNSAFE = [ acl(ANYONE_ID_UNSAFE, Perms::ALL) ]
|
85
|
+
CREATOR_ALL_ACL = [ acl(AUTH_IDS, Perms::ALL) ]
|
86
|
+
READ_ACL_UNSAFE = [ acl(ANYONE_ID_UNSAFE, Perms::READ) ]
|
87
|
+
|
88
|
+
# The Anyone ID
|
89
|
+
ID_ANYONE_UNSAFE = ANYONE_ID_UNSAFE
|
90
|
+
|
91
|
+
# Represents the set of auth ids for the current connection
|
92
|
+
ID_USE_AUTHS = AUTH_IDS
|
93
|
+
|
94
|
+
ACL_OPEN_UNSAFE = OPEN_ACL_UNSAFE
|
95
|
+
ACL_CREATOR_ALL = CREATOR_ALL_ACL
|
96
|
+
ACL_READ_UNSAFE = READ_ACL_UNSAFE
|
97
|
+
|
98
|
+
|
99
|
+
def self.seq_to_path(path,id)
|
100
|
+
format("%s%010d",path,id)
|
101
|
+
end
|
102
|
+
|
103
|
+
def self.path_to_seq(path)
|
104
|
+
matches = /^(.*)(\d{10})$/.match(path)
|
105
|
+
matches ? [matches[1],matches[2].to_i] : [path,nil]
|
106
|
+
end
|
107
|
+
|
108
|
+
CURRENT = :zookeeper_current
|
109
|
+
# Main method for connecting to a client
|
110
|
+
# @param addresses [Array<String>] list of host:port for the ZK cluster as Array or comma separated String
|
111
|
+
# @option options [Class] :binding binding optional implementation class
|
112
|
+
# either {EventMachine::Binding} or {RubyIO::Binding} but normally autodiscovered
|
113
|
+
# @option options [String] :chroot chroot path.
|
114
|
+
# All client calls will be made relative to this path
|
115
|
+
# @option options [Watcher] :watch the default watcher
|
116
|
+
# @option options [String] :scheme the authentication scheme
|
117
|
+
# @option options [String] :auth the authentication credentials
|
118
|
+
# @yieldparam [Client]
|
119
|
+
# @return [Client]
|
120
|
+
def self.connect(addresses,options={},&block)
|
121
|
+
if options.has_key?(:binding)
|
122
|
+
binding_type = options[:binding]
|
123
|
+
else
|
124
|
+
binding_type = @bindings.detect { |b| b.available? }
|
125
|
+
raise ProtocolError,"No available binding" unless binding_type
|
126
|
+
end
|
127
|
+
binding = binding_type.new()
|
128
|
+
session = Session.new(binding,addresses,options)
|
129
|
+
client = Client.new(binding)
|
130
|
+
binding.start(client,session)
|
131
|
+
|
132
|
+
return client unless block_given?
|
133
|
+
|
134
|
+
binding_type.context() do |storage|
|
135
|
+
@binding_storage = storage
|
136
|
+
storage.current[CURRENT] ||= []
|
137
|
+
storage.current[CURRENT].push(client)
|
138
|
+
begin
|
139
|
+
block.call(client)
|
140
|
+
ensure
|
141
|
+
storage.current[CURRENT].pop
|
142
|
+
client.close() unless session.closed?
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# within the block supplied to {#connect} this will return the
|
148
|
+
# current ZK client
|
149
|
+
def self.current
|
150
|
+
#We'd use if key? here if strand supported it
|
151
|
+
@binding_storage.current[CURRENT].last if @binding_storage.current[CURRENT]
|
152
|
+
end
|
153
|
+
|
154
|
+
class WatchEvent
|
155
|
+
attr_reader :watch_types
|
156
|
+
def initialize(watch_types)
|
157
|
+
@watch_types = watch_types
|
158
|
+
end
|
159
|
+
|
160
|
+
include Enumeration
|
161
|
+
enum :none,(-1),[]
|
162
|
+
enum :node_created, 1, [ :data, :exists ]
|
163
|
+
enum :node_deleted, 2, [ :data, :children ]
|
164
|
+
enum :node_data_changed, 3, [ :data, :exists ]
|
165
|
+
enum :node_children_changed, 4, [ :children ]
|
166
|
+
end
|
167
|
+
|
168
|
+
class KeeperState
|
169
|
+
include Enumeration
|
170
|
+
|
171
|
+
enum :disconnected, 0
|
172
|
+
enum :connected, 3
|
173
|
+
enum :auth_failed, 4
|
174
|
+
enum :expired, (-112)
|
175
|
+
end
|
176
|
+
|
177
|
+
|
178
|
+
# @abstract.
|
179
|
+
class Watcher
|
180
|
+
# @param [KeeperState] state representing the session state
|
181
|
+
# (:connected, :disconnected, :auth_failed, :session_expired)
|
182
|
+
# @param [String] path the effected path
|
183
|
+
# @param [WatchEvent] event the event that triggered the watch
|
184
|
+
def process_watch(state,path,event)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
#@private
|
189
|
+
module Operations
|
190
|
+
CREATE_OPTS = { :sequential => 2, :ephemeral => 1 }
|
191
|
+
|
192
|
+
private
|
193
|
+
def op_create(path,data,acl,*modeopts,&callback)
|
194
|
+
return synchronous_call(:op_create,path,data,acl,*modeopts)[0] unless block_given?
|
195
|
+
flags = modeopts.inject(0) { |flags,opt|
|
196
|
+
raise ArgumentError, "Unknown create option #{ opt }" unless CREATE_OPTS.has_key?(opt)
|
197
|
+
flags | CREATE_OPTS[opt]
|
198
|
+
}
|
199
|
+
path = session.chroot(path)
|
200
|
+
|
201
|
+
req = Proto::CreateRequest.new(:path => path, :data => data, :acl => acl, :flags => flags)
|
202
|
+
queue_request(req,:create,1,Proto::CreateResponse) do | response |
|
203
|
+
callback.call(session.unchroot(response.path)) if callback
|
204
|
+
end
|
205
|
+
|
206
|
+
end
|
207
|
+
|
208
|
+
def op_set(path,data,version,&callback)
|
209
|
+
return synchronous_call(:op_set,path,data,version)[0] unless block_given?
|
210
|
+
path = session.chroot(path)
|
211
|
+
|
212
|
+
req = Proto::SetDataRequest.new(:path => path, :data => data, :version => version)
|
213
|
+
queue_request(req,:set_data,5,Proto::SetDataResponse) do | response |
|
214
|
+
callback.call( response.stat ) if callback
|
215
|
+
end
|
216
|
+
|
217
|
+
end
|
218
|
+
|
219
|
+
def op_delete(path,version,&callback)
|
220
|
+
return synchronous_call(:op_delete,path,version) unless block_given?
|
221
|
+
path = session.chroot(path)
|
222
|
+
|
223
|
+
req = Proto::DeleteRequest.new(:path => path, :version => version)
|
224
|
+
queue_request(req,:delete,2) do |response|
|
225
|
+
callback.call() if callback
|
226
|
+
end
|
227
|
+
|
228
|
+
end
|
229
|
+
|
230
|
+
def op_check(path,version,&callback)
|
231
|
+
return synchronous_call(:op_check,path,version) unless block_given?
|
232
|
+
path = session.chroot(path)
|
233
|
+
req = Proto::CheckVersionRequest.new(:path => path, :version => version)
|
234
|
+
queue_request(req,:check,13) do |response|
|
235
|
+
callback.call() if callback
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
end
|
240
|
+
|
241
|
+
# Client API
|
242
|
+
#
|
243
|
+
# All calls operate asynchronously or synchronously based on whether a block is supplied
|
244
|
+
#
|
245
|
+
# Without a block, requests are executed synchronously and either return results directly or raise
|
246
|
+
# a {Error}
|
247
|
+
#
|
248
|
+
# With a block, the request returns immediately with a {AsyncOp}. When the server responds the
|
249
|
+
# block is passed the results. Errors will be sent to an error callback if registered on the {AsyncOp}
|
250
|
+
#
|
251
|
+
# Requests that take a watch argument can be passed either...
|
252
|
+
# * An object that quacks like a {Watcher}
|
253
|
+
# * A Proc will be invoked with arguments state, path, event
|
254
|
+
# * The literal value "true" refers to the default watcher registered with the session
|
255
|
+
#
|
256
|
+
# Registered watches will be fired exactly once for a given path with either the expected event
|
257
|
+
# or with state :expired and event :none when the session is finalised
|
258
|
+
class Client
|
259
|
+
|
260
|
+
include Operations
|
261
|
+
# @api private
|
262
|
+
# See {::ZooKeeper.connect}
|
263
|
+
def initialize(binding)
|
264
|
+
@binding = binding
|
265
|
+
end
|
266
|
+
|
267
|
+
# Session timeout, initially as supplied, but once connected is the negotiated
|
268
|
+
# timeout with the server.
|
269
|
+
def timeout
|
270
|
+
@binding.session.timeout
|
271
|
+
end
|
272
|
+
|
273
|
+
# The currently registered default watcher
|
274
|
+
def watcher
|
275
|
+
@binding.session.watcher
|
276
|
+
end
|
277
|
+
|
278
|
+
# Assign the watcher to the session. This watcher will receive session connect/disconnect/expired
|
279
|
+
# events as well as any path based watches registered to the API calls using the literal value "true"
|
280
|
+
# @param [Watcher|Proc] watcher
|
281
|
+
def watcher=(watcher)
|
282
|
+
@binding.session.watcher=watcher
|
283
|
+
end
|
284
|
+
|
285
|
+
# Retrieve the list of children at the given path
|
286
|
+
# @overload children(path,watch=nil)
|
287
|
+
# @param [String] path
|
288
|
+
# @param [Watcher] if supplied sets a child watch on the given path
|
289
|
+
# @return [Data::Stat,Array<String>] stat,children stat of path and the list of child nodes
|
290
|
+
# @raise [Error]
|
291
|
+
# @overload children(path,watch=nil)
|
292
|
+
# @return [AsyncOp] asynchronous operation
|
293
|
+
# @yieldparam [Data::Stat] stat current stat of path
|
294
|
+
# @yieldparam [Array<String>] children the list of child nodes at path
|
295
|
+
def children(path,watch=nil,&callback)
|
296
|
+
return synchronous_call(:children,path,watch) unless block_given?
|
297
|
+
path = chroot(path)
|
298
|
+
|
299
|
+
req = Proto::GetChildren2Request.new(:path => path, :watch => watch)
|
300
|
+
queue_request(req,:get_children2,12,Proto::GetChildren2Response,:children,watch) do | response |
|
301
|
+
callback.call(response.stat, response.children.to_a)
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
# Create a node
|
306
|
+
# @overload create(path,data,acl,*modeopts)
|
307
|
+
# Synchronous style
|
308
|
+
# @param [String] path the base name of the path to create
|
309
|
+
# @param [String] data the content to store at path
|
310
|
+
# @param [Data::ACL] acl the access control list to apply to the new node
|
311
|
+
# @param [Symbol,...] modeopts combination of :sequential, :emphemeral
|
312
|
+
# @return [String] the created path, only different if :sequential is requested
|
313
|
+
# @raise [Error]
|
314
|
+
# @overload create(path,data,acl,*modeopts)
|
315
|
+
# @return [AsyncOp] asynchronous operation
|
316
|
+
# @yieldparam [String] path the created path
|
317
|
+
def create(path,data,acl,*modeopts,&callback)
|
318
|
+
op_create(path,data,acl,*modeopts,&callback)
|
319
|
+
end
|
320
|
+
|
321
|
+
# Retrieve data
|
322
|
+
# @overload get(path,watch=nil)
|
323
|
+
# @param [String] path
|
324
|
+
# @param [Watcher] watch optional data watch to set on this path
|
325
|
+
# @return [Data::Stat,String] stat,data at path
|
326
|
+
# @raise [Error]
|
327
|
+
# @overload get(path,watch=nil)
|
328
|
+
# @return [AsyncOp] asynchronous operation
|
329
|
+
# @yieldparam [Data::Stat] stat Stat of the path
|
330
|
+
# @yieldparam [String] data Content at path
|
331
|
+
def get(path,watch=nil,&blk)
|
332
|
+
return synchronous_call(:get,path,watch) unless block_given?
|
333
|
+
path = chroot(path)
|
334
|
+
|
335
|
+
req = Proto::GetDataRequest.new(:path => path, :watch => watch)
|
336
|
+
|
337
|
+
queue_request(req,:get,4,Proto::GetDataResponse,:data,watch) do | response |
|
338
|
+
blk.call( response.stat, response.data.to_s)
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
# Retrieve the {Data::Stat} of a path, or nil if the path does not exist
|
343
|
+
# @overload exists(path,watch=nil)
|
344
|
+
# @param [String] path
|
345
|
+
# @param [Watcher] wath optional exists watch to set on this path
|
346
|
+
# @return [Data::Stat] Stat of the path or nil if the path does not exist
|
347
|
+
# @raise [Error]
|
348
|
+
# @overload exists(path,watch=nil)
|
349
|
+
# @return [AsyncOp] asynchronous operation
|
350
|
+
# @yieldparam [Data:Stat] stat Stat of the path or nil if the path did not exist
|
351
|
+
def exists(path,watch=nil,&blk)
|
352
|
+
return synchronous_call(:exists,path,watch)[0] unless block_given?
|
353
|
+
path = chroot(path)
|
354
|
+
|
355
|
+
req = Proto::ExistsRequest.new(:path => path, :watch => watch)
|
356
|
+
queue_request(req,:exists,3,Proto::ExistsResponse,:exists,watch,ExistsPacket) do | response |
|
357
|
+
blk.call( response.nil? ? nil : response.stat )
|
358
|
+
end
|
359
|
+
end
|
360
|
+
alias :exists? :exists
|
361
|
+
alias :stat :exists
|
362
|
+
|
363
|
+
# Delete path
|
364
|
+
# @overload delete(path,version)
|
365
|
+
# @param [String] path
|
366
|
+
# @param [FixNum] version the expected version to be deleted (-1 to match any version)
|
367
|
+
# @return
|
368
|
+
# @raise [Error]
|
369
|
+
# @overload delete(path,version)
|
370
|
+
# @return [AsyncOp]
|
371
|
+
# @yield [] callback invoked if delete is successful
|
372
|
+
def delete(path,version,&callback)
|
373
|
+
op_delete(path,version,&callback)
|
374
|
+
end
|
375
|
+
|
376
|
+
# Set Data
|
377
|
+
# @overload set(path,data,version)
|
378
|
+
# @param [String] path
|
379
|
+
# @param [String] data content to set at path
|
380
|
+
# @param [Fixnum] version expected current version at path
|
381
|
+
# @return [Data::Stat] new stat of path (ie new version)
|
382
|
+
# @raise [Error]
|
383
|
+
# @overload set(path,data,version)
|
384
|
+
# @return [AsyncOp] asynchronous operation
|
385
|
+
# @yieldparam [Data::Stat] stat new stat of path
|
386
|
+
def set(path,data,version,&callback)
|
387
|
+
op_set(path,data,version,&callback)
|
388
|
+
end
|
389
|
+
|
390
|
+
# Get ACl
|
391
|
+
# @overload get_acl(path)
|
392
|
+
# @param [String] path
|
393
|
+
# @return [Array<Data::ACL>] list of acls applying to path
|
394
|
+
# @raise [Error]
|
395
|
+
# @overload get_acl(path)
|
396
|
+
# @return [AsyncOp] asynchronous operation
|
397
|
+
# @yieldparam [Array<Data::ACL>] list of acls applying to path
|
398
|
+
def get_acl(path,&blk)
|
399
|
+
return synchronous_call(:get_acl,path)[0] unless block_given?
|
400
|
+
path = chroot(path)
|
401
|
+
|
402
|
+
req = Proto::GetACLRequest.new(:path => path)
|
403
|
+
queue_request(req,:get_acl,6,Proto::GetACLResponse) do | response |
|
404
|
+
blk.call( response.acl )
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
# Set ACL
|
409
|
+
# @overload set_acl(path,acl,version)
|
410
|
+
# @param [String] path
|
411
|
+
# @param [Array<Data::ACL>] acl list of acls for path
|
412
|
+
# @param [Fixnum] version expected current version
|
413
|
+
# @return Data::Stat new stat for path if successful
|
414
|
+
# @raise [Error]
|
415
|
+
# @overload set_acl(path,acl,version)
|
416
|
+
# @return [AsyncOp] asynchronous operation
|
417
|
+
# @yieldparam [Data::Stat] new stat for path if successful
|
418
|
+
def set_acl(path,acl,version,&blk)
|
419
|
+
return synchronous_call(:set_acl,acl,version)[0] unless block_given?
|
420
|
+
path = chroot(path)
|
421
|
+
|
422
|
+
req = Proto::SetACLRequest.new(:path => path, :acl => acl, :version => version)
|
423
|
+
queue_request(req,:set_acl,7,Proto::SetACLResponse) do | response |
|
424
|
+
blk.call( response.stat )
|
425
|
+
end
|
426
|
+
|
427
|
+
end
|
428
|
+
|
429
|
+
# Synchronise path between session and leader
|
430
|
+
# @overload sync(path)
|
431
|
+
# @param [String] path
|
432
|
+
# @return [String] path
|
433
|
+
# @raise [Error]
|
434
|
+
# @overload sync(path)
|
435
|
+
# @return [AsyncOp] asynchronous operation
|
436
|
+
# @yieldparam [String] path
|
437
|
+
def sync(path,&blk)
|
438
|
+
return synchronous_call(:sync,path)[0] unless block_given?
|
439
|
+
path = chroot(path)
|
440
|
+
req = Proto::SyncRequest.new(:path => path)
|
441
|
+
queue_request(req,:sync,9,Proto::SyncResponse) do | response |
|
442
|
+
blk.call(unchroot(response.path))
|
443
|
+
end
|
444
|
+
end
|
445
|
+
|
446
|
+
# Close the session
|
447
|
+
# @overload close()
|
448
|
+
# @raise [Error]
|
449
|
+
# @overload close()
|
450
|
+
# @return [AsyncOp] asynchronous operation
|
451
|
+
# @yield [] callback invoked when session is closed
|
452
|
+
def close(&blk)
|
453
|
+
return synchronous_call(:close) unless block_given?
|
454
|
+
@binding.close(&blk)
|
455
|
+
end
|
456
|
+
|
457
|
+
# @api private
|
458
|
+
# See {#transaction}
|
459
|
+
def multi(ops,&callback)
|
460
|
+
return synchronous_call(:multi,ops) unless block_given?
|
461
|
+
|
462
|
+
req = Proto::MultiRequest.new()
|
463
|
+
|
464
|
+
ops.each do |op|
|
465
|
+
req.requests << { :header => { :_type => op.opcode, :done => false, :err=> 0 }, :request => op.request }
|
466
|
+
end
|
467
|
+
|
468
|
+
req.requests << { :header => { :_type => -1 , :done => true, :err => -1 } }
|
469
|
+
|
470
|
+
logger.debug("Multi #{req}")
|
471
|
+
queue_request(req,:multi,14,Proto::MultiResponse) do |response|
|
472
|
+
exception = nil
|
473
|
+
response.responses.each_with_index() do |multi_response,index|
|
474
|
+
next if multi_response.done?
|
475
|
+
op = ops[index]
|
476
|
+
if multi_response.header._type == -1
|
477
|
+
errcode = multi_response.header.err.to_i
|
478
|
+
if (errcode != 0)
|
479
|
+
exception = Error.lookup(errcode).exception("Transaction error for op ##{index} - #{op.op} (#{op.path})")
|
480
|
+
#TODO just raises the first exception
|
481
|
+
raise exception
|
482
|
+
end
|
483
|
+
else
|
484
|
+
callback_args = if multi_response.has_response? then [ multi_response.response ] else [] end
|
485
|
+
ops[index].callback.call(*callback_args)
|
486
|
+
end
|
487
|
+
end
|
488
|
+
end
|
489
|
+
end
|
490
|
+
|
491
|
+
# Perform multiple operations in a transaction
|
492
|
+
# @overload transaction()
|
493
|
+
# @return [Transaction]
|
494
|
+
# @overload transaction(&block)
|
495
|
+
# Execute the supplied block and commit the transaction (synchronously)
|
496
|
+
# @yieldparam [Transaction] txn
|
497
|
+
def transaction(&block)
|
498
|
+
txn = Transaction.new(self,session)
|
499
|
+
return txn unless block_given?
|
500
|
+
|
501
|
+
yield txn
|
502
|
+
txn.commit
|
503
|
+
end
|
504
|
+
private
|
505
|
+
|
506
|
+
def session
|
507
|
+
@binding.session
|
508
|
+
end
|
509
|
+
|
510
|
+
def synchronous_call(method,*args)
|
511
|
+
op = self.send(method,*args) do |*results|
|
512
|
+
results
|
513
|
+
end
|
514
|
+
op.backtrace = op.backtrace[2..-1] if op.backtrace
|
515
|
+
|
516
|
+
op.value
|
517
|
+
end
|
518
|
+
|
519
|
+
def queue_request(*args,&blk)
|
520
|
+
op = @binding.queue_request(*args,&blk)
|
521
|
+
op.backtrace = caller[1..-1]
|
522
|
+
op
|
523
|
+
end
|
524
|
+
|
525
|
+
def chroot(path)
|
526
|
+
session.chroot(path)
|
527
|
+
end
|
528
|
+
|
529
|
+
def unchroot(path)
|
530
|
+
session.unchroot(path)
|
531
|
+
end
|
532
|
+
|
533
|
+
end
|
534
|
+
|
535
|
+
# Collects zookeeper operations to execute as a single transaction
|
536
|
+
#
|
537
|
+
# The builder methods {#create} {#delete} {#check} {#set} all take
|
538
|
+
# an optional callback block that will be executed if the {#commit} succeeds.
|
539
|
+
#
|
540
|
+
# If the transaction fails none of these callbacks will be executed.
|
541
|
+
class Transaction
|
542
|
+
include Operations
|
543
|
+
#:nodoc
|
544
|
+
def initialize(client,session)
|
545
|
+
@client = client
|
546
|
+
@session = session
|
547
|
+
@ops = []
|
548
|
+
end
|
549
|
+
|
550
|
+
|
551
|
+
# Create a node
|
552
|
+
# @param [String] path the base name of the path to create
|
553
|
+
# @param [String] data the content to store at path
|
554
|
+
# @param [Data::ACL] acl the access control list to apply to the new node
|
555
|
+
# @param [Symbol,...] modeopts combination of :sequential, :emphemeral
|
556
|
+
# @yieldparam [String] path the created path
|
557
|
+
def create(path,data,acl,*modeopts,&callback)
|
558
|
+
op_create(path,data,acl,*modeopts,&callback)
|
559
|
+
end
|
560
|
+
|
561
|
+
# Delete path
|
562
|
+
# @param [String] path
|
563
|
+
# @param [FixNum] version the expected version to be deleted (-1 to match any version)
|
564
|
+
# @yield [] callback invoked if delete is successful
|
565
|
+
def delete(path,version,&callback)
|
566
|
+
op_delete(path,version,&callback)
|
567
|
+
end
|
568
|
+
|
569
|
+
# Set Data
|
570
|
+
# @param [String] path
|
571
|
+
# @param [String] data content to set at path
|
572
|
+
# @param [Fixnum] version expected current version at path or -1 for any version
|
573
|
+
# @yieldparam [Data::Stat] stat new stat of path
|
574
|
+
def set(path,data,version,&callback)
|
575
|
+
op_set(path,data,version,&callback)
|
576
|
+
end
|
577
|
+
|
578
|
+
# Check Version
|
579
|
+
def check(path,version,&callback)
|
580
|
+
op_check(path,version,&callback)
|
581
|
+
end
|
582
|
+
|
583
|
+
# Commit the transaction
|
584
|
+
# @overload commit()
|
585
|
+
# Synchronously commit the transaction
|
586
|
+
# @raise [Error] if the transaction failed
|
587
|
+
# @overload commit(&callback)
|
588
|
+
# @return [AsyncOp] captures the result of the asynchronous operation
|
589
|
+
# @yield [] callback invoked if transaction is successful
|
590
|
+
def commit(&callback)
|
591
|
+
op = client.multi(ops,&callback)
|
592
|
+
end
|
593
|
+
|
594
|
+
private
|
595
|
+
attr_reader :client, :session, :ops
|
596
|
+
|
597
|
+
def queue_request(request,op,opcode,response=nil,&callback)
|
598
|
+
ops << Operation.new(op,opcode,request,response,callback)
|
599
|
+
end
|
600
|
+
|
601
|
+
#Just ensure we have an empty block
|
602
|
+
def synchronous_call(method,*args)
|
603
|
+
self.send(method,*args) { |*results| results }
|
604
|
+
end
|
605
|
+
end
|
606
|
+
|
607
|
+
end
|
608
|
+
|