droid 1.0.2pre → 1.0.2
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/Rakefile +22 -22
- data/VERSION +1 -1
- data/bin/bleedq +1 -1
- data/droid.gemspec +2 -2
- data/examples/async_reply.rb +15 -15
- data/examples/heroku_async_reply.rb +12 -12
- data/examples/sync.rb +13 -13
- data/examples/worker.rb +46 -46
- data/lib/droid.rb +122 -122
- data/lib/droid/em.rb +53 -53
- data/lib/droid/heroku.rb +83 -83
- data/lib/droid/json_server.rb +104 -104
- data/lib/droid/monkey.rb +6 -6
- data/lib/droid/publish.rb +22 -25
- data/lib/droid/queue.rb +194 -194
- data/lib/droid/request.rb +106 -106
- data/lib/droid/sync.rb +74 -74
- data/lib/droid/utilization.rb +96 -96
- data/lib/droid/utils.rb +107 -107
- data/spec/publish_spec.rb +20 -20
- data/spec/response_spec.rb +43 -43
- data/spec/utils_spec.rb +39 -39
- data/spec/wait_for_port_spec.rb +9 -9
- metadata +5 -24
data/lib/droid/utils.rb
CHANGED
@@ -2,112 +2,112 @@ require 'socket'
|
|
2
2
|
require 'digest/md5'
|
3
3
|
|
4
4
|
class Droid
|
5
|
-
|
5
|
+
DEFAULT_TTL = 300
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
7
|
+
class BadPayload < RuntimeError; end
|
8
|
+
|
9
|
+
module Utils
|
10
|
+
def self.parse_message(raw)
|
11
|
+
return { } unless raw
|
12
|
+
JSON.parse(raw)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.parse_custom_headers(headers)
|
16
|
+
return { } unless headers
|
17
|
+
|
18
|
+
h = headers.dup
|
19
|
+
|
20
|
+
h[:published_on] = h[:published_on].to_i
|
21
|
+
|
22
|
+
h[:ttl] = h[:ttl].to_i rescue -1
|
23
|
+
h[:ttl] = -1 if h[:ttl] == 0
|
24
|
+
|
25
|
+
h
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.create_event_hash
|
29
|
+
s = Time.now.to_s + self.object_id.to_s + rand(100).to_s
|
30
|
+
'd' + Digest::MD5.hexdigest(s)
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.extract_custom_headers(hash, opts={}, popts={})
|
34
|
+
popts[:headers] ||= {}
|
35
|
+
headers = popts[:headers]
|
36
|
+
|
37
|
+
headers[:published_on] ||= hash.delete('published_on') || opts[:published_on] || Time.now.getgm.to_i
|
38
|
+
headers[:ttl] ||= hash.delete('ttl') || (opts[:ttl] || Droid::DEFAULT_TTL).to_i
|
39
|
+
headers[:reply_to] ||= opts[:reply_to] if opts[:reply_to]
|
40
|
+
|
41
|
+
# this is the event hash that gets transferred through various publish/reply actions
|
42
|
+
headers[:event_hash] ||= hash.delete('event_hash') || opts[:event_hash] || create_event_hash
|
43
|
+
|
44
|
+
# this value should be unique for each published/received message pair
|
45
|
+
headers[:message_id] ||= create_event_hash
|
46
|
+
|
47
|
+
# some strange behavior with integers makes it better to
|
48
|
+
# convert all amqp headers to strings to avoid any problems
|
49
|
+
headers.each { |k,v| headers[k] = v.to_s }
|
50
|
+
|
51
|
+
[hash, headers]
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.format_publish(data, opts={}, popts={})
|
55
|
+
raise Droid::BadPayload unless data.is_a?(Hash)
|
56
|
+
|
57
|
+
hash, headers = extract_custom_headers(data, opts, popts)
|
58
|
+
|
59
|
+
popts[:content_type] ||= 'application/json'
|
60
|
+
|
61
|
+
[hash.to_json, popts]
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.generate_queue(exchange_name, second_name=nil)
|
65
|
+
second_name ||= $$
|
66
|
+
"#{generate_name_for_instance(exchange_name)}.#{second_name}"
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.generate_name_for_instance(name)
|
70
|
+
"#{name}.#{Socket.gethostname}"
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.generate_reply_to(name)
|
74
|
+
"temp.reply.#{name}.#{self.generate_sym}"
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.generate_sym
|
78
|
+
values = [
|
79
|
+
rand(0x0010000),
|
80
|
+
rand(0x0010000),
|
81
|
+
rand(0x0010000),
|
82
|
+
rand(0x0010000),
|
83
|
+
rand(0x0010000),
|
84
|
+
rand(0x1000000),
|
85
|
+
rand(0x1000000),
|
86
|
+
]
|
87
|
+
"%04x%04x%04x%04x%04x%06x%06x" % values
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.data_summary(json)
|
91
|
+
return '-> (empty)' if json.empty?
|
92
|
+
summary = json.map do |k,v|
|
93
|
+
v = v.to_s
|
94
|
+
v = v[0..37] + '...' if v.size > 40
|
95
|
+
"#{k}=#{v}"
|
96
|
+
end.join(', ')
|
97
|
+
"-> #{summary}"
|
98
|
+
end
|
99
|
+
|
100
|
+
def self.format_data_summary(data, headers)
|
101
|
+
"(data) " + Droid::Utils.data_summary(data) + " (headers) " + Droid::Utils.data_summary(headers)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Need this to be backwards compatible
|
106
|
+
def self.gen_queue(droid, name)
|
107
|
+
dn = droid
|
108
|
+
dn = dn.name if dn.respond_to?(:name)
|
109
|
+
dn ||= "d"
|
110
|
+
dn = dn.gsub(" ", "")
|
111
|
+
Droid::Utils.generate_queue(name, droid)
|
112
|
+
end
|
113
113
|
end
|
data/spec/publish_spec.rb
CHANGED
@@ -1,27 +1,27 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe 'AMQP Publish' do
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
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
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
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
15
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
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
22
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
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
27
|
end
|
data/spec/response_spec.rb
CHANGED
@@ -1,47 +1,47 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe 'Request' do
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
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
47
|
end
|
data/spec/utils_spec.rb
CHANGED
@@ -1,43 +1,43 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe 'Utils' do
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
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
43
|
end
|
data/spec/wait_for_port_spec.rb
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
Thread.new do
|
4
|
-
|
5
|
-
|
4
|
+
sleep 2
|
5
|
+
TCPServer.new('localhost', 20_001).accept.close
|
6
6
|
end
|
7
7
|
|
8
8
|
describe 'Connectivity' do
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
9
|
+
it "waits for the rabbitmq server to come up before opening the amqp connection" do
|
10
|
+
start = Time.now
|
11
|
+
Droid.wait_for_tcp_port('localhost', 20_001)
|
12
|
+
finish = Time.now
|
13
|
+
(finish - start).should > 1
|
14
|
+
(finish - start).should < 3
|
15
|
+
end
|
16
16
|
end
|