zk-eventmachine 0.1.1

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,5 @@
1
+ A small place to hide goodies that I use while developing this
2
+
3
+ slyphon-project.vimrc is a local .vimrc file with abbrevs.
4
+ rvmrc is my .rvmrc
5
+
data/.dev_extras/rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/.dev_extras/rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm 1.9.2
@@ -0,0 +1,18 @@
1
+ iabbr _ZEM ZK::ZKEventMachine
2
+ iabbr _ZCB ZK::ZKEventMachine::Callback
3
+ iabbr _ZCLI ZK::ZKEventMachine::Client
4
+
5
+ iabbr _wia i.with_indifferent_access
6
+ iabbr _lam lambda {}hi
7
+
8
+ iabbr _seq i.should ==
9
+ iabbr _srex i.should raise_exception()i
10
+ iabbr _srexarg i.should raise_exception(ArgumentError)i
11
+ iabbr _snrex i.should_not raise_exception
12
+ iabbr _sbnil i.should be_nil
13
+
14
+ iabbr _desc describe do
15
+ iabbr _bef before do
16
+
17
+
18
+ " vim:ft=vim
data/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ .rvmrc
6
+ .vimrc
7
+ .rspec
8
+ *.log
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source "http://rubygems.org"
2
+ source 'http://localhost:50000'
3
+
4
+
5
+ # Specify your gem's dependencies in zk-em.gemspec
6
+ gemspec
7
+
8
+
9
+ # vim:ft=ruby
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (C) 2011 by Hewlett Packard Development Company, L.P.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,8 @@
1
+ ## OMG!
2
+
3
+ [ZK][] client implementation for [EventMachine][]
4
+
5
+
6
+ [ZK]: https://github.com/slyphon/zk
7
+ [EventMachine]: https://github.com/eventmachine/eventmachine
8
+
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
@@ -0,0 +1,256 @@
1
+ module ZK
2
+ module ZKEventMachine
3
+ # some improvements (one hopes) around the zookeeper gem's somewhat (ahem)
4
+ # minimal Callback class
5
+ #
6
+ module Callback
7
+
8
+ # Used by ZooKeeper to return an asynchronous result.
9
+ #
10
+ # If callbacks or errbacks are set on the instance, they will be called
11
+ # with just the data returned from the call (much like their synchronous
12
+ # versions).
13
+ #
14
+ # If a block was given to #new or #on_result, then that block is called
15
+ # with a ZK::Exceptions::KeeperException instance or nil, then the rest
16
+ # of the arguments defined for that callback type
17
+ #
18
+ # the node-style and deferred-style results are *NOT* exclusive, so if
19
+ # you use both _you will be called with results in both formats_.
20
+ #
21
+ class Base
22
+ include Deferred
23
+ include ZK::Logging
24
+
25
+ # set the result keys that should be used by node_style_result and to
26
+ # call the deferred_style_result blocks
27
+ #
28
+ def self.async_result_keys(*syms)
29
+ if syms.empty?
30
+ @async_result_keys || []
31
+ else
32
+ @async_result_keys = syms.map { |n| n.to_sym }
33
+ end
34
+ end
35
+
36
+ def initialize(prok=nil, &block)
37
+ on_result(prok, &block)
38
+ end
39
+
40
+ # register a block that should be called (node.js style) with the
41
+ # results
42
+ #
43
+ # @note replaces the block given to #new
44
+ #
45
+ def on_result(prok=nil, &block)
46
+ @block = (prok || block)
47
+ end
48
+
49
+ # Checks the return code from the async call. If the return code was not ZOK,
50
+ # then fire the errbacks and do the node-style error call
51
+ # otherwise, does nothing
52
+ def check_async_rc(hash)
53
+ call(hash) unless success?(hash)
54
+ end
55
+
56
+ # ZK will call this instance with a hash of data, which is the result
57
+ # of the asynchronous call. Depending on the style of callback in use,
58
+ # we take the appropriate actions
59
+ #
60
+ # delegates to #deferred_style_result and #node_style_result
61
+ def call(hash)
62
+ # logger.debug { "#{self.class.name}#call hash: #{hash.inspect}" }
63
+ EM.schedule do
64
+ deferred_style_result(hash)
65
+ node_style_result(hash)
66
+ end
67
+ end
68
+
69
+ # returns true if the request was successful (if return_code was Zookeeper::ZOK)
70
+ #
71
+ # @param [Hash] hash the result of the async call
72
+ #
73
+ # @returns [true, false] for success, failure
74
+ def success?(hash)
75
+ hash[:rc] == Zookeeper::ZOK
76
+ end
77
+
78
+ # Returns an instance of a sublcass ZK::Exceptions::KeeperException
79
+ # based on the asynchronous return_code.
80
+ #
81
+ # facilitates using case statements for error handling
82
+ #
83
+ # @param [Hash] hash the result of the async call
84
+ #
85
+ # @raise [RuntimeError] if the return_code is not known by ZK (this should never
86
+ # happen and if it does, you should report a bug)
87
+ #
88
+ # @return [ZK::Exceptions::KeeperException, nil] subclass based on
89
+ # return_code if there was an error, nil otherwise
90
+ #
91
+ def exception_for(hash)
92
+ return nil if success?(hash)
93
+ return_code = hash.fetch(:rc)
94
+ ZK::Exceptions::KeeperException.by_code(return_code).new
95
+ end
96
+
97
+ # @abstract should call set_deferred_status with the appropriate args
98
+ # for the result and type of call
99
+ def deferred_style_result(hash)
100
+ # ensure this calls the callback on the reactor
101
+
102
+ if success?(hash)
103
+ vals = hash.values_at(*async_result_keys)
104
+ # logger.debug { "#{self.class.name}#deferred_style_result async_result_keys: #{async_result_keys.inspect}, vals: #{vals.inspect}" }
105
+ self.succeed(*hash.values_at(*async_result_keys))
106
+ else
107
+ self.fail(exception_for(hash))
108
+ end
109
+ end
110
+
111
+ # call the user block with the correct Exception class as the first arg
112
+ # (or nil if no error) and then the appropriate args for the type of
113
+ # asynchronous call
114
+ def node_style_result(hash)
115
+ return unless @block
116
+ vals = hash.values_at(*async_result_keys)
117
+ # logger.debug { "#{self.class.name}#node_style_result async_result_keys: #{async_result_keys.inspect}, vals: #{vals.inspect}" }
118
+ if exc = exception_for(hash)
119
+ @block.call(exc)
120
+ else
121
+ @block.call(nil, *vals)
122
+ end
123
+ end
124
+
125
+ protected
126
+ def async_result_keys
127
+ self.class.async_result_keys
128
+ end
129
+ end
130
+
131
+ # used with Client#get call
132
+ class DataCallback < Base
133
+ async_result_keys :data, :stat
134
+ end
135
+
136
+ # used with Client#children call
137
+ class ChildrenCallback < Base
138
+ async_result_keys :strings, :stat
139
+ end
140
+
141
+ # used with Client#create
142
+ class StringCallback < Base
143
+ async_result_keys :string
144
+ end
145
+
146
+ # used with Client#stat and Client#exists?
147
+ class StatCallback < Base
148
+ async_result_keys :stat
149
+
150
+ # stat has a different concept of 'success', stat on a node that doesn't
151
+ # exist is not an exception, it's a certain kind of stat (like a null stat).
152
+ def success?(hash)
153
+ rc = hash[:rc]
154
+ (rc == Zookeeper::ZOK) || (rc == Zookeeper::ZNONODE)
155
+ end
156
+ end
157
+
158
+ # supports the syntactic sugar exists? call
159
+ class ExistsCallback < StatCallback
160
+ async_result_keys :stat
161
+
162
+ # @abstract should call set_deferred_status with the appropriate args
163
+ # for the result and type of call
164
+ def deferred_style_result(hash)
165
+ # ensure this calls the callback on the reactor
166
+
167
+ if success?(hash)
168
+ succeed(hash[:stat].exists?)
169
+ else
170
+ fail(exception_for(hash))
171
+ end
172
+ end
173
+
174
+ # call the user block with the correct Exception class as the first arg
175
+ # (or nil if no error) and then the appropriate args for the type of
176
+ # asynchronous call
177
+ def node_style_result(hash)
178
+ return unless @block
179
+ @block.call(exception_for(hash), hash[:stat].exists?)
180
+ end
181
+ end
182
+
183
+ # set operation returns a stat object, but in this case a NONODE is an
184
+ # error (unlike with StatCallback)
185
+ class SetCallback < Base
186
+ async_result_keys :stat
187
+ end
188
+
189
+ # used with Client#delete and Client#set_acl
190
+ class VoidCallback < Base
191
+ end
192
+
193
+ # used with Client#get_acl
194
+ class ACLCallback < Base
195
+ async_result_keys :acl, :stat
196
+ end
197
+
198
+ class << self
199
+ def new_data_cb(njs_block)
200
+ DataCallback.new(njs_block).tap do |cb| # create the callback with the user-provided block
201
+ cb.check_async_rc(yield(cb)) # yield the callback to the caller, check the return result
202
+ # of the async operation (not the async result)
203
+ end # return the callback
204
+ end
205
+ alias :new_get_cb :new_data_cb # create alias so that this matches the client API name
206
+
207
+ def new_string_cb(njs_block)
208
+ StringCallback.new(njs_block).tap do |cb|
209
+ cb.check_async_rc(yield(cb))
210
+ end
211
+ end
212
+ alias :new_create_cb :new_string_cb
213
+
214
+ def new_stat_cb(njs_block)
215
+ StatCallback.new(njs_block).tap do |cb|
216
+ cb.check_async_rc(yield(cb))
217
+ end
218
+ end
219
+
220
+ def new_exists_cb(njs_block)
221
+ ExistsCallback.new(njs_block).tap do |cb|
222
+ cb.check_async_rc(yield(cb))
223
+ end
224
+ end
225
+
226
+ def new_set_cb(njs_block)
227
+ SetCallback.new(njs_block).tap do |cb|
228
+ cb.check_async_rc(yield(cb))
229
+ end
230
+ end
231
+
232
+ def new_void_cb(njs_block)
233
+ VoidCallback.new(njs_block).tap do |cb|
234
+ cb.check_async_rc(yield(cb))
235
+ end
236
+ end
237
+ alias :new_delete_cb :new_void_cb
238
+ alias :new_set_acl_cb :new_void_cb
239
+
240
+ def new_children_cb(njs_block)
241
+ ChildrenCallback.new(njs_block).tap do |cb|
242
+ cb.check_async_rc(yield(cb))
243
+ end
244
+ end
245
+
246
+ def new_acl_cb(njs_block)
247
+ ACLCallback.new(njs_block).tap do |cb|
248
+ cb.check_async_rc(yield(cb))
249
+ end
250
+ end
251
+ alias :new_get_acl_cb :new_acl_cb
252
+ end
253
+ end
254
+ end
255
+ end
256
+
@@ -0,0 +1,122 @@
1
+ module ZK
2
+ module ZKEventMachine
3
+ class Client < ZK::Client::Base
4
+ include ZK::Logging
5
+ include Unixisms
6
+
7
+ DEFAULT_TIMEOUT = 10
8
+
9
+ attr_reader :client
10
+
11
+ # Takes same options as ZK::Client::Base
12
+ def initialize(host, opts={})
13
+ @host = host
14
+ @close_deferred = Deferred::Default.new
15
+ @event_handler = EventHandlerEM.new(self)
16
+ end
17
+
18
+ def on_close(&blk)
19
+ @close_deferred.callback(&blk) if blk
20
+ @close_deferred
21
+ end
22
+
23
+ # open a ZK connection, attach it to the reactor.
24
+ # returns an EM::Deferrable that will be called when the connection is
25
+ # ready for use
26
+ def connect(&blk)
27
+ # XXX: maybe move this into initialize, need to figure out how to schedule it properly
28
+ @cnx ||= ZookeeperEM::Client.new(@host, DEFAULT_TIMEOUT, event_handler.get_default_watcher_block).tap do |c|
29
+ c.on_attached { logger.debug { "connection is attached" } }
30
+ end
31
+
32
+ @cnx.on_attached(&blk)
33
+ end
34
+
35
+ def reopen(*a)
36
+ raise NotImplementedError, "reoopen is not implemented for the eventmachine version of the client"
37
+ end
38
+
39
+ def close!(&blk)
40
+ on_close(&blk)
41
+
42
+ if @cnx
43
+ logger.debug { "#{self.class.name}: calling @cnx.close" }
44
+ @cnx.close
45
+ @cnx = nil
46
+
47
+ logger.debug { "#{self.class.name}: @cnx.close callback fired, clearing event_handler" }
48
+ event_handler.clear!
49
+ logger.debug { "firing on_close handler" }
50
+ on_close.succeed
51
+ else
52
+ on_close.succeed
53
+ end
54
+
55
+ on_close
56
+ end
57
+ alias :close :close!
58
+
59
+ # get data at path, optionally enabling a watch on the node
60
+ #
61
+ # @returns [Callback] returns a Callback which is an EM::Deferred (so you
62
+ # can assign callbacks/errbacks) see Callback::Base for discussion
63
+ #
64
+ def get(path, opts={}, &block)
65
+ Callback.new_get_cb(block) do |cb|
66
+ super(path, opts.merge(:callback => cb))
67
+ end
68
+ end
69
+
70
+ def create(path, data='', opts={}, &block)
71
+ Callback.new_create_cb(block) do |cb|
72
+ super(path, data, opts.merge(:callback => cb))
73
+ end
74
+ end
75
+
76
+ def set(path, data, opts={}, &block)
77
+ Callback.new_set_cb(block) do |cb|
78
+ super(path, data, opts.merge(:callback => cb))
79
+ end
80
+ end
81
+
82
+ def stat(path, opts={}, &block)
83
+ cb_style = opts.delete(:cb_style) { |_| 'stat' }
84
+
85
+ meth = :"new_#{cb_style}_cb"
86
+
87
+ Callback.__send__(meth, block) do |cb|
88
+ super(path, opts.merge(:callback => cb))
89
+ end
90
+ end
91
+
92
+ def exists?(path, opts={}, &block)
93
+ stat(path, opts.merge(:cb_style => 'exists'), &block)
94
+ end
95
+
96
+ def delete(path, opts={}, &block)
97
+ Callback.new_delete_cb(block) do |cb|
98
+ super(path, opts.merge(:callback => cb))
99
+ end
100
+ end
101
+
102
+ def children(path, opts={}, &block)
103
+ Callback.new_children_cb(block) do |cb|
104
+ super(path, opts.merge(:callback => cb))
105
+ end
106
+ end
107
+
108
+ def get_acl(path, opts={}, &block)
109
+ Callback.new_get_acl_cb(block) do |cb|
110
+ super(path, opts.merge(:callback => cb))
111
+ end
112
+ end
113
+
114
+ def set_acl(path, acls, opts={}, &block)
115
+ Callback.new_set_acl_cb(block) do |cb|
116
+ super(path, acls, opts.merge(:callback => cb))
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
122
+
@@ -0,0 +1,39 @@
1
+ module ZK
2
+ module ZKEventMachine
3
+ module Deferred
4
+ include EM::Deferrable
5
+
6
+ # slight modification to EM::Deferrable,
7
+ #
8
+ # @returns [self] to allow for chaining
9
+ #
10
+ def callback(&block)
11
+ super(&block)
12
+ self
13
+ end
14
+
15
+ # @see #callback
16
+ def errback(&block)
17
+ super(&block)
18
+ self
19
+ end
20
+
21
+ # adds the block to both the callback and errback chains
22
+ def ensure_that(&block)
23
+ callback(&block)
24
+ errback(&block)
25
+ end
26
+
27
+ def chain_to(other_dfr, opts={})
28
+ other_dfr.callback { |*a| self.succeed(*a) }
29
+ other_dfr.errback { |*a| self.fail(*a) } unless opts[:ignore_errors]
30
+ self
31
+ end
32
+
33
+ class Default
34
+ include ZK::ZKEventMachine::Deferred
35
+ end
36
+ end
37
+ end
38
+ end
39
+
@@ -0,0 +1,34 @@
1
+ module ZK
2
+ module ZKEventMachine
3
+ # a small wrapper around the EventHandler instance, allowing us to
4
+ # deliver the event on the reactor thread, as opposed to calling it directly
5
+ #
6
+ class EventHandlerEM < ZK::EventHandler
7
+ include ZK::Logging
8
+
9
+ def register(path, &block)
10
+ # use the supplied block, but ensure that it gets called on the reactor
11
+ # thread
12
+ new_blk = lambda do |*a|
13
+ EM.schedule { block.call(*a) }
14
+ end
15
+
16
+ super(path, &new_blk)
17
+ end
18
+ alias :subscribe :register
19
+
20
+ def process(event)
21
+ EM.schedule { super(event) }
22
+ end
23
+
24
+ protected
25
+ # we're running on the Reactor, don't need to synchronize (hah, hah, we'll see...)
26
+ #
27
+ def synchronize
28
+ yield
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+