zkruby 3.4.3
Sign up to get free protection for your applications and to get access to all the features.
- data.tar.gz.sig +0 -0
- data/.gemtest +0 -0
- data/History.txt +18 -0
- data/Manifest.txt +39 -0
- data/README.rdoc +119 -0
- data/Rakefile +39 -0
- data/jute/jute.citrus +105 -0
- data/jute/lib/hoe/jute.rb +56 -0
- data/jute/lib/jute.rb +120 -0
- data/lib/em_zkruby.rb +4 -0
- data/lib/jute/zookeeper.rb +203 -0
- data/lib/zkruby.rb +4 -0
- data/lib/zkruby/bindata.rb +45 -0
- data/lib/zkruby/client.rb +608 -0
- data/lib/zkruby/enum.rb +108 -0
- data/lib/zkruby/eventmachine.rb +186 -0
- data/lib/zkruby/multi.rb +47 -0
- data/lib/zkruby/protocol.rb +182 -0
- data/lib/zkruby/rubyio.rb +310 -0
- data/lib/zkruby/session.rb +445 -0
- data/lib/zkruby/util.rb +141 -0
- data/lib/zkruby/zkruby.rb +27 -0
- data/spec/bindata_spec.rb +51 -0
- data/spec/enum_spec.rb +84 -0
- data/spec/eventmachine_spec.rb +50 -0
- data/spec/multi_spec.rb +93 -0
- data/spec/protocol_spec.rb +72 -0
- data/spec/recipe_helper.rb +68 -0
- data/spec/rubyio_spec.rb +8 -0
- data/spec/sequences_spec.rb +19 -0
- data/spec/server_helper.rb +45 -0
- data/spec/shared/auth.rb +40 -0
- data/spec/shared/basic.rb +180 -0
- data/spec/shared/binding.rb +33 -0
- data/spec/shared/chroot.rb +61 -0
- data/spec/shared/multi.rb +38 -0
- data/spec/shared/util.rb +56 -0
- data/spec/shared/watch.rb +49 -0
- data/spec/spec_helper.rb +12 -0
- data/src/jute/zookeeper.jute +288 -0
- data/yard_ext/enum_handler.rb +16 -0
- metadata +243 -0
- metadata.gz.sig +0 -0
data/lib/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
|
+
|