fraggle-spanx 4.0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,33 @@
1
+ require 'rubygems'
2
+ require 'fraggle'
3
+
4
+ EM.run do
5
+ c = Fraggle.connect do |c, err|
6
+ if err
7
+ fail err.message
8
+ end
9
+
10
+ c.rev do |v|
11
+ # Valid
12
+ req = c.walk(v, "/ctl/node/**") do |ents, err|
13
+ if err
14
+ p [:err, err]
15
+ else
16
+ ents.each do |e|
17
+ puts File.join(req.path, e.path) + "=" + e.value
18
+ end
19
+ end
20
+ end
21
+
22
+ # Limit 0 return nothing
23
+ c.walk(v, "/ctl/node/**", 0, 0) do |ents, err|
24
+ p [:nothing, ents, err]
25
+ end
26
+
27
+ # Error
28
+ c.walk(v, "/nothere") do |ents, err|
29
+ p [:nothere, ents, err]
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,16 @@
1
+ require 'rubygems'
2
+ require 'fraggle'
3
+
4
+ EM.run do
5
+ c = Fraggle.connect do |c, err|
6
+ if err
7
+ fail err.message
8
+ end
9
+
10
+ c.rev do |v|
11
+ c.watch(v, "/ctl/node/**") do |e, err|
12
+ p [e, err]
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,28 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "fraggle/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "fraggle-spanx"
7
+ s.version = Fraggle::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Blake Mizerany"]
10
+ s.email = ["blake.mizerany@gmail.com"]
11
+ s.homepage = ""
12
+ s.summary = %q{An EventMachine Client for Doozer}
13
+ s.description = s.summary
14
+
15
+ s.rubyforge_project = "fraggle"
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ["lib"]
21
+
22
+ s.add_dependency "beefcake-spanx"
23
+ s.add_dependency "eventmachine"
24
+
25
+ s.add_development_dependency "turn"
26
+ s.add_development_dependency "statsample"
27
+ s.add_development_dependency "rake"
28
+ end
@@ -0,0 +1,59 @@
1
+ require 'fraggle/client'
2
+
3
+ module Fraggle
4
+ include Response::Err
5
+
6
+ Clobber = Client::MaxInt64
7
+
8
+ DEFAULT_URI = "doozer:?" + [
9
+ "ca=127.0.0.1:8046",
10
+ "ca=127.0.0.1:8041",
11
+ "ca=127.0.0.1:8042",
12
+ "ca=127.0.0.1:8043"
13
+ ].join("&")
14
+
15
+ def self.connect(uri=nil, &blk)
16
+ uri = uri || ENV["DOOZER_URI"] || DEFAULT_URI
17
+
18
+ addrs, sk = uri(uri)
19
+
20
+ if addrs.length == 0
21
+ raise ArgumentError, "there were no addrs supplied in the uri (#{uri.inspect})"
22
+ end
23
+
24
+ addr = addrs.shift
25
+ host, port = addr.split(":")
26
+
27
+ cn = EM.connect(host, port, Connection, addr)
28
+ c = Client.new(cn, addrs)
29
+ c.access(sk) do |_, err|
30
+ if err
31
+ blk.call(nil, err)
32
+ else
33
+ blk.call(c, nil)
34
+ end
35
+ end
36
+ end
37
+
38
+ def self.uri(u)
39
+ addrs, sk = [], ""
40
+
41
+ if u =~ /^doozer:\?(.*)$/
42
+ parts = $1.split("&")
43
+ parts.each do |pt|
44
+ k, v = pt.split("=")
45
+ case k
46
+ when "ca"
47
+ addrs << v
48
+ when "sk"
49
+ sk = v
50
+ end
51
+ end
52
+ else
53
+ raise ArgumentError, "invalid doozer uri"
54
+ end
55
+
56
+ [addrs, sk]
57
+ end
58
+
59
+ end
@@ -0,0 +1,211 @@
1
+ require 'eventmachine'
2
+ require 'fraggle/connection'
3
+
4
+ module Fraggle
5
+ class Client
6
+ include Request::Verb
7
+
8
+ MaxInt64 = 1<<63 - 1
9
+
10
+ attr_reader :cn, :addrs
11
+
12
+ def initialize(cn, addrs)
13
+ @cn, @addrs = cn, addrs
14
+ @attempt = Proc.new {|_| true }
15
+ end
16
+
17
+ def addr
18
+ cn.addr
19
+ end
20
+
21
+ def set(rev, path, value, &blk)
22
+ req = Request.new
23
+ req.verb = SET
24
+ req.rev = rev
25
+ req.path = path
26
+ req.value = value
27
+
28
+ idemp(req, &blk)
29
+ end
30
+
31
+ def get(rev, path, &blk)
32
+ req = Request.new
33
+ req.verb = GET
34
+ req.rev = rev
35
+ req.path = path
36
+
37
+ resend(req, &blk)
38
+ end
39
+
40
+ def del(rev, path, &blk)
41
+ req = Request.new
42
+ req.verb = DEL
43
+ req.rev = rev
44
+ req.path = path
45
+
46
+ idemp(req, &blk)
47
+ end
48
+
49
+ def _getdir(rev, path, offset, &blk)
50
+ req = Request.new
51
+ req.verb = GETDIR
52
+ req.rev = rev
53
+ req.path = path
54
+ req.offset = offset
55
+
56
+ resend(req, &blk)
57
+ end
58
+
59
+ def _walk(rev, path, offset, &blk)
60
+ req = Request.new
61
+ req.verb = WALK
62
+ req.rev = rev
63
+ req.path = path
64
+ req.offset = offset
65
+
66
+ resend(req, &blk)
67
+ end
68
+
69
+ def wait(rev, path, &blk)
70
+ req = Request.new
71
+ req.verb = WAIT
72
+ req.rev = rev
73
+ req.path = path
74
+
75
+ resend(req, &blk)
76
+ end
77
+
78
+ def rev(&blk)
79
+ req = Request.new
80
+ req.verb = REV
81
+
82
+ resend(req) do |v, _|
83
+ blk.call(v.rev)
84
+ end
85
+ end
86
+
87
+ def stat(rev, path, &blk)
88
+ req = Request.new
89
+ req.rev = rev
90
+ req.verb = STAT
91
+ req.path = path
92
+
93
+ resend(req, &blk)
94
+ end
95
+
96
+ def access(secret, &blk)
97
+ req = Request.new
98
+ req.verb = ACCESS
99
+ req.value = secret
100
+
101
+ resend(req, &blk)
102
+ end
103
+
104
+ def watch(rev, path, &blk)
105
+ wait(rev, path) do |e, err|
106
+ blk.call(e, err)
107
+ if ! err
108
+ watch(e.rev+1, path, &blk)
109
+ end
110
+ end
111
+ end
112
+
113
+ def getdir(rev, path, off=0, lim=MaxInt64, ents=[], &blk)
114
+ all(:_getdir, rev, path, off, lim, ents, &blk)
115
+ end
116
+
117
+ def walk(rev, path, off=0, lim=MaxInt64, ents=[], &blk)
118
+ all(:_walk, rev, path, off, lim, ents, &blk)
119
+ end
120
+
121
+ def all(m, rev, path, off, lim, ents=[], &blk)
122
+ # We're decrementing lim as we go, so we need to return
123
+ # the accumulated values
124
+ if lim == 0
125
+ cn.next_tick { blk.call(ents, nil) }
126
+ return
127
+ end
128
+
129
+ __send__(m, rev, path, off) do |e, err|
130
+ case err && err.code
131
+ when nil
132
+ all(m, rev, path, off+1, lim-1, ents << e, &blk)
133
+ when Fraggle::Response::Err::RANGE
134
+ blk.call(ents, nil)
135
+ else
136
+ blk.call(nil, e)
137
+ end
138
+ end
139
+ end
140
+
141
+ # Sends a request to the server. Returns the request with a new tag
142
+ # assigned.
143
+ def send(req, &blk)
144
+ cb = Proc.new do |e, err|
145
+ case err
146
+ when Connection::DisconnectedError
147
+ if cn.err?
148
+ reconnect!
149
+ end
150
+ end
151
+ blk.call(e, err)
152
+ end
153
+
154
+ cn.send_request(req, cb)
155
+ end
156
+
157
+ def resend(req, &blk)
158
+ send(req) do |e, err|
159
+ case err
160
+ when Connection::DisconnectedError
161
+ req.tag = nil
162
+ resend(req, &blk)
163
+ else
164
+ blk.call(e, err)
165
+ end
166
+ end
167
+ end
168
+
169
+ def idemp(req, &blk)
170
+ send(req) do |e, err|
171
+ case err
172
+ when Connection::DisconnectedError
173
+ # If we're trying to update a value that isn't missing or that we're
174
+ # not trying to clobber, it's safe to retry. We can't idempotently
175
+ # update missing values because there may be a race with another
176
+ # client that sets and/or deletes the key during the time between your
177
+ # read and write.
178
+ if (req.rev || 0) > 0
179
+ req.tag = nil
180
+ idemp(req, &blk)
181
+ else
182
+ blk.call(e, err)
183
+ end
184
+ else
185
+ blk.call(e, err)
186
+ end
187
+ end
188
+ end
189
+
190
+ ##
191
+ # Setting `blk` will cause a client to call it before attempting to reconnect.
192
+ # `blk` is called with one parameter `addr`, which is the address that will be
193
+ # for reconnect.
194
+ def attempt(&blk)
195
+ @attempt = blk
196
+ end
197
+
198
+ def reconnect!
199
+ addr = @addrs.slice(rand(@addrs.length))
200
+ if @attempt.call(addr)
201
+ reconnect(addr)
202
+ end
203
+ end
204
+
205
+ def reconnect(addr)
206
+ host, port = addr.split(":")
207
+ @cn = EM.connect(host, port, Fraggle::Connection, addr)
208
+ end
209
+
210
+ end
211
+ end
@@ -0,0 +1,124 @@
1
+ require 'fraggle/response'
2
+
3
+ module Fraggle
4
+
5
+ module Connection
6
+
7
+ # Raised when a request is invalid
8
+ class SendError < StandardError
9
+ def initialize(req, msg=nil)
10
+ @req = req
11
+ super(msg)
12
+ end
13
+ end
14
+
15
+ class DisconnectedError < StandardError
16
+ def ==(o)
17
+ return false if ! o.kind_of?(self.class)
18
+ message == o.message
19
+ end
20
+ end
21
+
22
+ class ResponseError < StandardError
23
+ attr_reader :code
24
+
25
+ alias :detail :message
26
+
27
+ def initialize(res)
28
+ @code = res.err_code
29
+ super("#{res.name_for(Response::Err, code)}: #{res.err_detail}")
30
+ end
31
+
32
+ def ==(o)
33
+ return false if ! o.kind_of?(self.class)
34
+ code == o.code && message == o.message
35
+ end
36
+ end
37
+
38
+
39
+ attr_reader :addr
40
+
41
+ def initialize(addr)
42
+ @addr, @cb = addr, {}
43
+ end
44
+
45
+ def receive_data(data)
46
+ (@buf ||= "") << data
47
+
48
+ while @buf.length > 0
49
+ if @len && @buf.length >= @len
50
+ bytes = @buf.slice!(0, @len)
51
+ @len = nil
52
+ res = Response.decode(bytes)
53
+ receive_response(res)
54
+ elsif @buf.length >= 4
55
+ bytes = @buf.slice!(0, 4)
56
+ @len = bytes.unpack("N")[0]
57
+ else
58
+ break
59
+ end
60
+ end
61
+ end
62
+
63
+ # The default receive_response
64
+ def receive_response(res)
65
+ return if err?
66
+ req, blk = @cb.delete(res.tag)
67
+ return if ! blk
68
+ if res.err_code
69
+ blk.call(nil, ResponseError.new(res))
70
+ else
71
+ blk.call(res, nil)
72
+ end
73
+ end
74
+
75
+ def send_request(req, blk)
76
+ if req.tag
77
+ raise SendError, "Already sent #{req.inspect}"
78
+ end
79
+
80
+ if err?
81
+ next_tick { blk.call(nil, DisconnectedError.new(self.addr)) }
82
+ return req
83
+ end
84
+
85
+ req.tag = 0
86
+ while @cb.has_key?(req.tag)
87
+ req.tag += 1
88
+
89
+ req.tag %= 2**31
90
+ end
91
+
92
+ # TODO: remove this!
93
+ @cb[req.tag] = [req, blk]
94
+
95
+ data = req.encode
96
+ head = [data.length].pack("N")
97
+
98
+ send_data("#{head}#{data}")
99
+
100
+ req
101
+ end
102
+
103
+ def unbind
104
+ @err = true
105
+ @cb.values.each do |_, blk|
106
+ blk.call(nil, DisconnectedError.new(self.addr))
107
+ end
108
+ end
109
+
110
+ def err?
111
+ !!@err
112
+ end
113
+
114
+ def timer(n, &blk)
115
+ EM.add_timer(n, &blk)
116
+ end
117
+
118
+ def next_tick(&blk)
119
+ EM.next_tick(&blk)
120
+ end
121
+
122
+ end
123
+
124
+ end