zkruby 3.4.3

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