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.
@@ -0,0 +1,4 @@
1
+ # This is the main require for standard ruby io/thread based binding
2
+
3
+ require 'zkruby/zkruby'
4
+ require 'zkruby/eventmachine'
@@ -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
@@ -0,0 +1,4 @@
1
+ # This is the main require for standard ruby io/thread based binding
2
+
3
+ require 'zkruby/zkruby'
4
+ require 'zkruby/rubyio'
@@ -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
+