droid 0.9.5 → 1.0.0
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.
- data/.gitignore +2 -0
- data/Rakefile +38 -0
- data/VERSION +1 -0
- data/bin/bleedq +19 -0
- data/droid.gemspec +100 -0
- data/examples/async_reply.rb +25 -0
- data/examples/heroku_async_reply.rb +22 -0
- data/examples/sync.rb +32 -0
- data/examples/worker.rb +58 -0
- data/lib/droid.rb +88 -476
- data/lib/droid/em.rb +55 -0
- data/lib/droid/heroku.rb +102 -0
- data/lib/droid/heroku/local_stats.rb +145 -0
- data/{vendor/logger_client/lib → lib/droid/heroku}/logger_client.rb +9 -5
- data/lib/droid/heroku/memcache_cluster.rb +129 -0
- data/lib/droid/heroku/stats.rb +30 -0
- data/lib/droid/json_server.rb +109 -0
- data/lib/droid/monkey.rb +8 -0
- data/lib/droid/publish.rb +24 -0
- data/lib/droid/queue.rb +196 -0
- data/lib/droid/request.rb +110 -0
- data/lib/droid/sync.rb +88 -0
- data/lib/droid/utilization.rb +113 -0
- data/lib/droid/utils.rb +113 -0
- data/lib/heroku_droid.rb +1 -86
- data/lib/local_stats.rb +1 -143
- data/lib/memcache_cluster.rb +1 -129
- data/lib/stats.rb +1 -30
- data/spec/publish_spec.rb +27 -0
- data/spec/response_spec.rb +47 -0
- data/spec/spec_helper.rb +11 -0
- data/spec/utils_spec.rb +43 -0
- data/{test/wait_for_port_test.rb → spec/wait_for_port_spec.rb} +2 -9
- metadata +109 -35
- data/README.md +0 -34
- data/lib/utilization.rb +0 -90
- data/test/base.rb +0 -43
- data/test/droid_test.rb +0 -53
- data/test/heroku_droid_test.rb +0 -42
- data/vendor/logger_client/Rakefile +0 -53
- data/vendor/logger_client/init.rb +0 -1
- data/vendor/logger_client/test.rb +0 -18
data/lib/memcache_cluster.rb
CHANGED
@@ -1,129 +1 @@
|
|
1
|
-
require '
|
2
|
-
require 'yaml'
|
3
|
-
require 'digest/sha1'
|
4
|
-
|
5
|
-
# Manages a pool of memcache servers. This class should not be called
|
6
|
-
# outside of the reactor - it does not account for asynchronous access
|
7
|
-
# to the server list.
|
8
|
-
module MemcacheCluster
|
9
|
-
extend self
|
10
|
-
|
11
|
-
HEROKU_NAMESPACE = '0Xfa15837Z' # heroku's internal memcache namespace
|
12
|
-
|
13
|
-
# A MemCache object configured with heroku's internal memcache namespace.
|
14
|
-
def heroku
|
15
|
-
cache(HEROKU_NAMESPACE)
|
16
|
-
end
|
17
|
-
|
18
|
-
def cache_retry(prefix, opts={})
|
19
|
-
opts[:retries] ||= 5
|
20
|
-
opts[:delay] ||= 0.5
|
21
|
-
|
22
|
-
retried = 0
|
23
|
-
begin
|
24
|
-
c = cache(prefix)
|
25
|
-
yield c if block_given?
|
26
|
-
rescue MemCache::MemCacheError => e
|
27
|
-
Log.error "#{e.class} -> #{e.message}", :exception => e
|
28
|
-
raise if retried > opts[:retries]
|
29
|
-
retried += 1
|
30
|
-
sleep opts[:delay]
|
31
|
-
@caches = { }
|
32
|
-
retry
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
def set(prefix, *args)
|
37
|
-
res = nil
|
38
|
-
cache_retry(prefix) do |c|
|
39
|
-
res = c.set(*args)
|
40
|
-
end
|
41
|
-
res
|
42
|
-
end
|
43
|
-
|
44
|
-
def get(prefix, *args)
|
45
|
-
res = nil
|
46
|
-
cache_retry(prefix) do |c|
|
47
|
-
res = c.get(*args)
|
48
|
-
end
|
49
|
-
res
|
50
|
-
end
|
51
|
-
|
52
|
-
# Create listeners for standard memcache cluster related topics.
|
53
|
-
def attach(droid, file='memcached.yml')
|
54
|
-
load_from_file(file)
|
55
|
-
|
56
|
-
droid.listen4('memcache.up', :queue => "memcache.up.#{LocalStats.this_instance_name}.#$$") { |msg| add(msg['address'], msg['port']) }
|
57
|
-
droid.listen4('instance.down', :queue => "instance.down.#{LocalStats.this_instance_name}.#$$") { |msg| remove(msg['local_ip']) if msg['slot'] == 'memcache' }
|
58
|
-
EM.add_timer(1) { droid.publish('memcache.needed', {}) }
|
59
|
-
end
|
60
|
-
|
61
|
-
# A MemCache object configured with the given prefix.
|
62
|
-
def cache(prefix, options={})
|
63
|
-
caches[prefix] ||=
|
64
|
-
MemCache.new(servers, options.merge(:namespace => prefix))
|
65
|
-
end
|
66
|
-
|
67
|
-
alias_method :[], :cache
|
68
|
-
|
69
|
-
def caches
|
70
|
-
reload_if_stale
|
71
|
-
@caches ||= {}
|
72
|
-
end
|
73
|
-
|
74
|
-
def servers
|
75
|
-
reload_if_stale
|
76
|
-
@servers ||= []
|
77
|
-
end
|
78
|
-
|
79
|
-
def add(ip, port)
|
80
|
-
host = [ip, port].join(':')
|
81
|
-
return if servers.include?(host)
|
82
|
-
|
83
|
-
log { "#{host} added" }
|
84
|
-
@servers.push host
|
85
|
-
@servers.sort!
|
86
|
-
@caches = {}
|
87
|
-
write_to_file
|
88
|
-
@last_read = Time.now
|
89
|
-
end
|
90
|
-
|
91
|
-
def remove(host)
|
92
|
-
if servers.reject!{ |s| s =~ /^#{host}/ }
|
93
|
-
log { "#{host} removed" }
|
94
|
-
caches.clear
|
95
|
-
write_to_file
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
def reload_if_stale
|
100
|
-
if @last_read &&
|
101
|
-
(Time.now - @last_read) > 5 &&
|
102
|
-
File.mtime(@file) > @last_read
|
103
|
-
log { "server list modified. reloading." }
|
104
|
-
load_from_file(@file)
|
105
|
-
end
|
106
|
-
rescue => boom
|
107
|
-
# ignore errors accessing/reading file.
|
108
|
-
end
|
109
|
-
|
110
|
-
def load_from_file(file)
|
111
|
-
@file = file
|
112
|
-
@last_read = Time.now
|
113
|
-
@servers = YAML.load(File.read(file)) rescue []
|
114
|
-
@caches = {}
|
115
|
-
end
|
116
|
-
|
117
|
-
def write_to_file
|
118
|
-
log { "writing server list: #{@file}" }
|
119
|
-
File.open(@file, 'w') do |f|
|
120
|
-
f.flock(File::LOCK_EX)
|
121
|
-
f.write YAML.dump(@servers)
|
122
|
-
f.flock(File::LOCK_UN)
|
123
|
-
end
|
124
|
-
end
|
125
|
-
|
126
|
-
def log(type=:debug)
|
127
|
-
Log.send(type, "memcached: #{yield}")
|
128
|
-
end
|
129
|
-
end
|
1
|
+
require 'droid/heroku/memcache_cluster'
|
data/lib/stats.rb
CHANGED
@@ -1,30 +1 @@
|
|
1
|
-
|
2
|
-
# The MemCache instance used to manipulate stats.
|
3
|
-
def cache
|
4
|
-
MemcacheCluster.cache("heroku:stats")
|
5
|
-
end
|
6
|
-
|
7
|
-
# Increment a stat counter. If the counter does not exist,
|
8
|
-
# yield to the block and use the result as the current counter
|
9
|
-
# value. With no block, the counter will be started at zero.
|
10
|
-
def increment(key, amount=1)
|
11
|
-
if (value = cache.incr(key, amount)).nil?
|
12
|
-
value = yield if block_given?
|
13
|
-
value = (value || 0) + amount
|
14
|
-
cache.add(key, value.to_s, 0, true)
|
15
|
-
end
|
16
|
-
rescue => boom
|
17
|
-
Log.default_error(boom)
|
18
|
-
nil
|
19
|
-
end
|
20
|
-
|
21
|
-
# Set the stat counter to a specific value.
|
22
|
-
def sample(key, value)
|
23
|
-
cache.set(key, value.to_s, 0, true)
|
24
|
-
rescue => boom
|
25
|
-
Log.default_error(boom)
|
26
|
-
nil
|
27
|
-
end
|
28
|
-
|
29
|
-
extend self
|
30
|
-
end
|
1
|
+
require 'droid/heroku/stats'
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'AMQP Publish' do
|
4
|
+
before do
|
5
|
+
@json, @publish_opts = Droid::Utils.format_publish({:x => 1, :y => 2}, {}, {})
|
6
|
+
Droid::Utils.stubs(:format_publish).with({:x => 1, :y => 2}, {}, {}).returns([@json, @publish_opts])
|
7
|
+
end
|
8
|
+
|
9
|
+
it "publishes a message to a queue" do
|
10
|
+
@q = mock('queue')
|
11
|
+
::MQ.expects(:queue).with('topic').returns(@q)
|
12
|
+
@q.expects(:publish).with(@json, @publish_opts)
|
13
|
+
Droid.publish_to_q('topic', :x => 1, :y => 2)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "publishes a message to an exchange" do
|
17
|
+
@ex = mock('exchange')
|
18
|
+
::MQ.expects(:direct).with('topic').returns(@ex)
|
19
|
+
@ex.expects(:publish).with(@json, @publish_opts)
|
20
|
+
Droid.publish_to_ex('topic', :x => 1, :y => 2)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "by default publishes to an exchange" do
|
24
|
+
Droid.expects(:publish_to_ex).with('topic', {:x => 1, :y => 2}, {}, {})
|
25
|
+
Droid.publish('topic', :x => 1, :y => 2)
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Request' do
|
4
|
+
before do
|
5
|
+
@mq = mock("MQ")
|
6
|
+
@exchange = mock("MQ Exchange")
|
7
|
+
@queue = mock("MQ Queue")
|
8
|
+
|
9
|
+
@qobj = mock("Droid::BasicQueue Instance")
|
10
|
+
@qobj.stubs(:q).returns(@queue)
|
11
|
+
@qobj.stubs(:mq).returns(@mq)
|
12
|
+
@qobj.stubs(:ex).returns(@exchange)
|
13
|
+
|
14
|
+
@header = mock("amqp header", :headers => {})
|
15
|
+
|
16
|
+
@raw_message = '{"x":123,"y":"abc"}'
|
17
|
+
|
18
|
+
@res = Droid::Request.new(@qobj, @header, @raw_message)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "allows access to response via symbols or strings in array access" do
|
22
|
+
@res['x'].should == 123
|
23
|
+
@res[:x].should == 123
|
24
|
+
@res['y'].should == 'abc'
|
25
|
+
@res[:y].should == 'abc'
|
26
|
+
end
|
27
|
+
|
28
|
+
it "calls ack on the header" do
|
29
|
+
@header.expects(:ack)
|
30
|
+
@res.ack
|
31
|
+
end
|
32
|
+
|
33
|
+
it "calls q on the qobj" do
|
34
|
+
@qobj.expects(:q)
|
35
|
+
@res.q
|
36
|
+
end
|
37
|
+
|
38
|
+
it "calls ex on the qobj" do
|
39
|
+
@qobj.expects(:ex)
|
40
|
+
@res.ex
|
41
|
+
end
|
42
|
+
|
43
|
+
it "calls mq on the qobj" do
|
44
|
+
@qobj.expects(:mq)
|
45
|
+
@res.mq
|
46
|
+
end
|
47
|
+
end
|
data/spec/spec_helper.rb
ADDED
data/spec/utils_spec.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Utils' do
|
4
|
+
before do
|
5
|
+
end
|
6
|
+
|
7
|
+
it "parses a json message" do
|
8
|
+
Droid::Utils.parse_message('{"x":1,"y":2}').should == {'x' => 1, 'y' => 2}
|
9
|
+
end
|
10
|
+
|
11
|
+
it "parses custom headers and force integers on a few values" do
|
12
|
+
headers = {
|
13
|
+
:x => '1',
|
14
|
+
:y => '2',
|
15
|
+
:reply_to => 'q.random.reply',
|
16
|
+
:published_on => '12345',
|
17
|
+
:event_hash => 'x123'
|
18
|
+
}
|
19
|
+
|
20
|
+
h = Droid::Utils.parse_custom_headers(headers)
|
21
|
+
|
22
|
+
h.size.should == 6
|
23
|
+
h[:ttl].should == -1
|
24
|
+
h[:reply_to].should == 'q.random.reply'
|
25
|
+
h[:published_on].should == 12345
|
26
|
+
h[:event_hash].should == 'x123'
|
27
|
+
end
|
28
|
+
|
29
|
+
it "raises an exception if the data to format for publish is not a hash" do
|
30
|
+
lambda { Droid::Utils.format_publish('bad payload') }.should.raise Droid::BadPayload
|
31
|
+
end
|
32
|
+
|
33
|
+
it "generates a name for the instance" do
|
34
|
+
Socket.stubs(:gethostname).returns('deepblue.123')
|
35
|
+
Droid::Utils.generate_name_for_instance('woo').should == 'woo.deepblue.123'
|
36
|
+
end
|
37
|
+
|
38
|
+
it "generates queue name, really more like a generic identifier" do
|
39
|
+
Socket.stubs(:gethostname).returns('deepblue.123')
|
40
|
+
Droid::Utils.generate_queue('topic').should == "topic.deepblue.123.#{$$}"
|
41
|
+
Droid::Utils.generate_queue('topic', 'local').should == "topic.deepblue.123.local"
|
42
|
+
end
|
43
|
+
end
|
@@ -1,18 +1,11 @@
|
|
1
|
-
require
|
2
|
-
require File.dirname(__FILE__) + '/base'
|
3
|
-
|
4
|
-
require 'rubygems'
|
5
|
-
require 'thread'
|
6
|
-
require 'bacon'
|
7
|
-
|
8
|
-
Bacon.summary_on_exit
|
1
|
+
require 'spec_helper'
|
9
2
|
|
10
3
|
Thread.new do
|
11
4
|
sleep 2
|
12
5
|
TCPServer.new('localhost', 20_001).accept.close
|
13
6
|
end
|
14
7
|
|
15
|
-
describe
|
8
|
+
describe 'Connectivity' do
|
16
9
|
it "waits for the rabbitmq server to come up before opening the amqp connection" do
|
17
10
|
start = Time.now
|
18
11
|
Droid.wait_for_tcp_port('localhost', 20_001)
|
metadata
CHANGED
@@ -3,22 +3,22 @@ name: droid
|
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
|
+
- 1
|
6
7
|
- 0
|
7
|
-
-
|
8
|
-
|
9
|
-
version: 0.9.5
|
8
|
+
- 0
|
9
|
+
version: 1.0.0
|
10
10
|
platform: ruby
|
11
|
-
authors:
|
12
|
-
|
11
|
+
authors:
|
12
|
+
- Ricardo Chimal, Jr.
|
13
13
|
autorequire:
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-
|
18
|
-
default_executable:
|
17
|
+
date: 2010-10-21 00:00:00 -07:00
|
18
|
+
default_executable: bleedq
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
|
-
name:
|
21
|
+
name: baconmocha
|
22
22
|
prerelease: false
|
23
23
|
requirement: &id001 !ruby/object:Gem::Requirement
|
24
24
|
requirements:
|
@@ -27,67 +27,136 @@ dependencies:
|
|
27
27
|
segments:
|
28
28
|
- 0
|
29
29
|
version: "0"
|
30
|
-
type: :
|
30
|
+
type: :development
|
31
31
|
version_requirements: *id001
|
32
32
|
- !ruby/object:Gem::Dependency
|
33
|
-
name:
|
33
|
+
name: json_pure
|
34
34
|
prerelease: false
|
35
35
|
requirement: &id002 !ruby/object:Gem::Requirement
|
36
36
|
requirements:
|
37
37
|
- - ">="
|
38
38
|
- !ruby/object:Gem::Version
|
39
39
|
segments:
|
40
|
+
- 1
|
41
|
+
- 2
|
40
42
|
- 0
|
41
|
-
version:
|
43
|
+
version: 1.2.0
|
42
44
|
type: :runtime
|
43
45
|
version_requirements: *id002
|
44
46
|
- !ruby/object:Gem::Dependency
|
45
|
-
name:
|
47
|
+
name: rest-client
|
46
48
|
prerelease: false
|
47
49
|
requirement: &id003 !ruby/object:Gem::Requirement
|
48
50
|
requirements:
|
49
51
|
- - ">="
|
50
52
|
- !ruby/object:Gem::Version
|
51
53
|
segments:
|
54
|
+
- 1
|
55
|
+
- 2
|
52
56
|
- 0
|
53
|
-
version:
|
57
|
+
version: 1.2.0
|
54
58
|
type: :runtime
|
55
59
|
version_requirements: *id003
|
56
60
|
- !ruby/object:Gem::Dependency
|
57
|
-
name:
|
61
|
+
name: amqp
|
58
62
|
prerelease: false
|
59
63
|
requirement: &id004 !ruby/object:Gem::Requirement
|
60
64
|
requirements:
|
61
|
-
- - "
|
65
|
+
- - "="
|
62
66
|
- !ruby/object:Gem::Version
|
63
67
|
segments:
|
64
68
|
- 0
|
65
|
-
|
69
|
+
- 6
|
70
|
+
- 7
|
71
|
+
version: 0.6.7
|
66
72
|
type: :runtime
|
67
73
|
version_requirements: *id004
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
74
|
+
- !ruby/object:Gem::Dependency
|
75
|
+
name: bunny
|
76
|
+
prerelease: false
|
77
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ~>
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
segments:
|
82
|
+
- 0
|
83
|
+
- 6
|
84
|
+
- 0
|
85
|
+
version: 0.6.0
|
86
|
+
type: :runtime
|
87
|
+
version_requirements: *id005
|
88
|
+
- !ruby/object:Gem::Dependency
|
89
|
+
name: SystemTimer
|
90
|
+
prerelease: false
|
91
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ~>
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
segments:
|
96
|
+
- 1
|
97
|
+
- 2
|
98
|
+
- 0
|
99
|
+
version: 1.2.0
|
100
|
+
type: :runtime
|
101
|
+
version_requirements: *id006
|
102
|
+
- !ruby/object:Gem::Dependency
|
103
|
+
name: eventmachine_httpserver
|
104
|
+
prerelease: false
|
105
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - "="
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
segments:
|
110
|
+
- 0
|
111
|
+
- 2
|
112
|
+
- 0
|
113
|
+
version: 0.2.0
|
114
|
+
type: :runtime
|
115
|
+
version_requirements: *id007
|
116
|
+
description: Easy to use AMQP Library with constructs for typical usage patterns
|
117
|
+
email: ricardo@heroku.com
|
118
|
+
executables:
|
119
|
+
- bleedq
|
72
120
|
extensions: []
|
73
121
|
|
74
|
-
extra_rdoc_files:
|
75
|
-
|
122
|
+
extra_rdoc_files: []
|
123
|
+
|
76
124
|
files:
|
125
|
+
- .gitignore
|
126
|
+
- Rakefile
|
127
|
+
- VERSION
|
128
|
+
- bin/bleedq
|
129
|
+
- droid.gemspec
|
130
|
+
- examples/async_reply.rb
|
131
|
+
- examples/heroku_async_reply.rb
|
132
|
+
- examples/sync.rb
|
133
|
+
- examples/worker.rb
|
77
134
|
- lib/droid.rb
|
135
|
+
- lib/droid/em.rb
|
136
|
+
- lib/droid/heroku.rb
|
137
|
+
- lib/droid/heroku/local_stats.rb
|
138
|
+
- lib/droid/heroku/logger_client.rb
|
139
|
+
- lib/droid/heroku/memcache_cluster.rb
|
140
|
+
- lib/droid/heroku/stats.rb
|
141
|
+
- lib/droid/json_server.rb
|
142
|
+
- lib/droid/monkey.rb
|
143
|
+
- lib/droid/publish.rb
|
144
|
+
- lib/droid/queue.rb
|
145
|
+
- lib/droid/request.rb
|
78
146
|
- lib/droid/sync.rb
|
147
|
+
- lib/droid/utilization.rb
|
148
|
+
- lib/droid/utils.rb
|
79
149
|
- lib/heroku_droid.rb
|
80
150
|
- lib/local_stats.rb
|
81
151
|
- lib/memcache_cluster.rb
|
82
152
|
- lib/stats.rb
|
83
|
-
-
|
84
|
-
-
|
85
|
-
-
|
86
|
-
-
|
87
|
-
-
|
88
|
-
- README.md
|
153
|
+
- spec/publish_spec.rb
|
154
|
+
- spec/response_spec.rb
|
155
|
+
- spec/spec_helper.rb
|
156
|
+
- spec/utils_spec.rb
|
157
|
+
- spec/wait_for_port_spec.rb
|
89
158
|
has_rdoc: true
|
90
|
-
homepage:
|
159
|
+
homepage: http://heroku.com
|
91
160
|
licenses: []
|
92
161
|
|
93
162
|
post_install_message:
|
@@ -111,13 +180,18 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
111
180
|
version: "0"
|
112
181
|
requirements: []
|
113
182
|
|
114
|
-
rubyforge_project:
|
183
|
+
rubyforge_project:
|
115
184
|
rubygems_version: 1.3.6
|
116
185
|
signing_key:
|
117
186
|
specification_version: 3
|
118
|
-
summary: AMQP
|
187
|
+
summary: AMQP Wrapper Library
|
119
188
|
test_files:
|
120
|
-
-
|
121
|
-
-
|
122
|
-
-
|
123
|
-
-
|
189
|
+
- spec/publish_spec.rb
|
190
|
+
- spec/response_spec.rb
|
191
|
+
- spec/spec_helper.rb
|
192
|
+
- spec/utils_spec.rb
|
193
|
+
- spec/wait_for_port_spec.rb
|
194
|
+
- examples/async_reply.rb
|
195
|
+
- examples/heroku_async_reply.rb
|
196
|
+
- examples/sync.rb
|
197
|
+
- examples/worker.rb
|