mbus 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.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/README.mediawiki +169 -0
  3. data/Rakefile +24 -0
  4. data/bin/console +11 -0
  5. data/bin/messagebus_swarm +77 -0
  6. data/lib/messagebus.rb +62 -0
  7. data/lib/messagebus/client.rb +166 -0
  8. data/lib/messagebus/cluster_map.rb +161 -0
  9. data/lib/messagebus/connection.rb +118 -0
  10. data/lib/messagebus/consumer.rb +447 -0
  11. data/lib/messagebus/custom_errors.rb +37 -0
  12. data/lib/messagebus/dottable_hash.rb +113 -0
  13. data/lib/messagebus/error_status.rb +42 -0
  14. data/lib/messagebus/logger.rb +45 -0
  15. data/lib/messagebus/message.rb +168 -0
  16. data/lib/messagebus/messagebus_types.rb +107 -0
  17. data/lib/messagebus/producer.rb +187 -0
  18. data/lib/messagebus/swarm.rb +49 -0
  19. data/lib/messagebus/swarm/controller.rb +296 -0
  20. data/lib/messagebus/swarm/drone.rb +195 -0
  21. data/lib/messagebus/swarm/drone/logging_worker.rb +53 -0
  22. data/lib/messagebus/validations.rb +68 -0
  23. data/lib/messagebus/version.rb +36 -0
  24. data/messagebus.gemspec +29 -0
  25. data/spec/messagebus/client_spec.rb +157 -0
  26. data/spec/messagebus/cluster_map_spec.rb +178 -0
  27. data/spec/messagebus/consumer_spec.rb +338 -0
  28. data/spec/messagebus/dottable_hash_spec.rb +137 -0
  29. data/spec/messagebus/message_spec.rb +93 -0
  30. data/spec/messagebus/producer_spec.rb +147 -0
  31. data/spec/messagebus/swarm/controller_spec.rb +73 -0
  32. data/spec/messagebus/validations_spec.rb +71 -0
  33. data/spec/spec_helper.rb +10 -0
  34. data/vendor/gems/stomp.rb +23 -0
  35. data/vendor/gems/stomp/client.rb +360 -0
  36. data/vendor/gems/stomp/connection.rb +583 -0
  37. data/vendor/gems/stomp/errors.rb +39 -0
  38. data/vendor/gems/stomp/ext/hash.rb +24 -0
  39. data/vendor/gems/stomp/message.rb +68 -0
  40. metadata +138 -0
@@ -0,0 +1,137 @@
1
+ require 'messagebus/dottable_hash'
2
+
3
+ describe Messagebus::DottableHash do
4
+ let (:dottable_hash) { Messagebus::DottableHash.new }
5
+
6
+ it "is a wrapper of Hash" do
7
+ Messagebus::DottableHash.ancestors.should include(Hash)
8
+ end
9
+
10
+ it "optionally takes a hash in the constructor" do
11
+ Messagebus::DottableHash.new({:this => "works"})
12
+ end
13
+
14
+ it "allows dot assignment" do
15
+ dottable_hash.foo = "bar"
16
+ dottable_hash.should == {"foo" => "bar"}
17
+ end
18
+
19
+ it "allows dot access" do
20
+ dottable_hash.foo = "baz"
21
+ dottable_hash.foo.should == "baz"
22
+ end
23
+
24
+ it "allows bracket assignment with a String key" do
25
+ dottable_hash["merica"] = "the free"
26
+ dottable_hash.should == {"merica" => "the free"}
27
+ end
28
+
29
+ it "allows bracket access with a String key" do
30
+ dottable_hash["foo"] = "hey"
31
+ dottable_hash["foo"].should == "hey"
32
+ end
33
+
34
+ it "allows bracket assignment with a Symbol key" do
35
+ dottable_hash[:patrick] = "gombert"
36
+ dottable_hash.should == {"patrick" => "gombert"}
37
+ end
38
+
39
+ it "allows bracket access with a Symbol key" do
40
+ dottable_hash[:patrick] = "gombert"
41
+ dottable_hash[:patrick].should == "gombert"
42
+ end
43
+
44
+ it "converts keys for Hashes upon bracket assignment" do
45
+ dottable_hash[:dave] = {:surname => {:original => "moore"}}
46
+ dottable_hash.dave.surname.original.should == "moore"
47
+ end
48
+
49
+ it "remains dottable after merging a plain ruby Hash" do
50
+ dottable_hash.merge!({:some => "plain old hash"})
51
+ dottable_hash.some.should == "plain old hash"
52
+ end
53
+
54
+ it "converts all keys in nested Hashes of any depth" do
55
+ dottable_hash.merge!({:some => {:plain => {:old => "hash"}}})
56
+ dottable_hash.should == {"some" => {"plain" => {"old" => "hash"}}}
57
+ end
58
+
59
+ it "converts all keys nested in an Array to Strings" do
60
+ dottable_hash.merge!({:some => [{:plain => "old hash"}]})
61
+ dottable_hash.should == {"some" => [{"plain" => "old hash"}]}
62
+ end
63
+
64
+ it "converts all keys in nested Arrays of any depth" do
65
+ dottable_hash.merge!({:some => [[{:plain => "old hash"}], "foo"]})
66
+ dottable_hash.should == {"some" => [[{"plain" => "old hash"}], "foo"]}
67
+ end
68
+
69
+ it "ignores elements which are not Hashes or Arrays" do
70
+ dottable_hash.merge!({:some => [{:plain => "old hash"}, "foo"]})
71
+ dottable_hash.should == {"some" => [{"plain" => "old hash"}, "foo"]}
72
+ end
73
+
74
+ it "makes deeply-nested hash structures dottable upon initialization" do
75
+ dottable_hash = Messagebus::DottableHash.new({:some => {:plain => {:old => "hash"}}})
76
+ dottable_hash.some.plain.old.should == "hash"
77
+ end
78
+
79
+ it "makes deeply Array-nested Hash structures dottable upon initialization" do
80
+ dottable_hash = Messagebus::DottableHash.new({:some => [[{:plain => "old hash"}]]})
81
+ dottable_hash.some[0][0].plain.should == "old hash"
82
+ end
83
+
84
+ it "#replace converts a plain old ruby hash to a dottable hash" do
85
+ replacement = {:dave => {:moore => "awesome"}}
86
+ result = Messagebus::DottableHash.new({ "foo" => "bar" }).replace(replacement)
87
+ result.dave.moore.should == "awesome"
88
+ result.has_key?("foo").should == false
89
+ end
90
+
91
+ it "#update converts a plain old ruby hash to a dottable hash" do
92
+ result = Messagebus::DottableHash.new({ "foo" => "bar" }).update({:something => :else})
93
+ result.should == {"something" => :else, "foo" => "bar"}
94
+ end
95
+
96
+ it "#merge converts a plain old ruby hash to a dottable hash" do
97
+ result = Messagebus::DottableHash.new({ "foo" => "bar" }).merge({:something => :else})
98
+ result.should == {"something" => :else, "foo" => "bar"}
99
+ end
100
+
101
+ it "#delete accepts Symbol as an argument" do
102
+ dottable_hash["foo"] = { "bar" => "bar", "baz" => "baz" }
103
+ dottable_hash.foo.delete(:bar)
104
+ dottable_hash.should == { "foo" => { "baz" => "baz" } }
105
+ end
106
+
107
+ it "#has_key? accepts Symbol as an argument" do
108
+ Messagebus::DottableHash.new({ "foo" => "bar" }).has_key?(:foo).should == true
109
+ end
110
+
111
+ it "#key? accepts a Symbol as an argument" do
112
+ Messagebus::DottableHash.new({ "foo" => "bar" }).key?(:foo).should == true
113
+ end
114
+
115
+ it "#fetch accepts a Symbol as an argument" do
116
+ Messagebus::DottableHash.new({ "foo" => "bar" }).fetch(:foo).should == "bar"
117
+ end
118
+
119
+ it "#include? accepts a Symbol as an argument" do
120
+ Messagebus::DottableHash.new({ "foo" => "bar" }).include?(:foo).should == true
121
+ end
122
+
123
+ it "#store accepts a Symbol as an argument" do
124
+ hash = Messagebus::DottableHash.new({ "foo" => "bar" })
125
+ hash.store(:bears, "scare me")
126
+ hash.bears.should == "scare me"
127
+ end
128
+
129
+ it "#values_at accepts Symbols as arguments" do
130
+ dottable_hash.merge({ "foo" => "bar", "zen" => "cool", "hey" => "now" })
131
+ dottable_hash.values_at(:foo, :zen).should =~ ["bar", "cool"]
132
+ end
133
+
134
+ it "#respond_to? returns true for everything" do
135
+ dottable_hash.respond_to?(:whatever).should == true
136
+ end
137
+ end
@@ -0,0 +1,93 @@
1
+ # encoding: UTF-8
2
+ require File.join(File.dirname(__FILE__), '..', 'spec_helper')
3
+
4
+ describe Messagebus::Message do
5
+ describe "when checking non-ascii payload" do
6
+ describe "when checking conversions" do
7
+ before(:each) do
8
+ @msg_string_data1 = 'am – 5:00 pm,'
9
+ @gb2312_string = "\xC4\xE3\xBA\xC3"
10
+ @msg_string_data2 = 'ASCII String'
11
+ @msg_json_object = {:a=> '1', :b => {:x => 'Thorbjørn am – 5:00 pm,'}}
12
+ @msg_binary_data = "\xE5\xA5\xBD"
13
+ end
14
+
15
+ it "check non-ascii string" do
16
+ message = Messagebus::Message.create(@msg_string_data1)
17
+ message2 = Messagebus::Message.get_message_from_thrift_binary(message.to_thrift_binary)
18
+ @msg_string_data1.bytes.to_a.should == message2.payload.bytes.to_a
19
+ end
20
+
21
+ it "check ASCII string" do
22
+ if RUBY_VERSION.to_f >= 1.9
23
+ @msg_string_data2.force_encoding("ASCII")
24
+ end
25
+ message = Messagebus::Message.create(@msg_string_data2)
26
+ message2 = Messagebus::Message.get_message_from_thrift_binary(message.to_thrift_binary)
27
+ @msg_string_data2.bytes.to_a.should == message2.payload.bytes.to_a
28
+
29
+ end
30
+
31
+ it "check gb2312 string" do
32
+ if RUBY_VERSION.to_f >= 1.9
33
+ @gb2312_string.force_encoding("GB2312")
34
+ end
35
+ message = Messagebus::Message.create(@gb2312_string)
36
+ message2 = Messagebus::Message.get_message_from_thrift_binary(message.to_thrift_binary)
37
+ @gb2312_string.bytes.to_a.should == message2.payload.bytes.to_a
38
+ end
39
+
40
+ it "check json thrift conversions" do
41
+ message = Messagebus::Message.create(@msg_json_object)
42
+ message2 = Messagebus::Message.get_message_from_thrift_binary(message.to_thrift_binary)
43
+ json_string = @msg_json_object.to_json
44
+ json_string.bytes.to_a.should == message2.payload.bytes.to_a
45
+ end
46
+
47
+ it "check binary thrift equality" do
48
+ message = Messagebus::Message.create(@msg_binary_data, nil, true)
49
+ message2 = Messagebus::Message.get_message_from_thrift_binary(message.to_thrift_binary)
50
+ @msg_binary_data.bytes.to_a == message2.payload.bytes.to_a
51
+
52
+ end
53
+
54
+ it "check message id are unique" do
55
+ message_ids = Set.new
56
+ (1..100).each do |i|
57
+ message = Messagebus::Message.create(@msg_binary_data)
58
+ message_ids.include?(message.message_id).should == false
59
+ message_ids.add(message.message_id)
60
+ end
61
+ end
62
+ end
63
+ end
64
+ describe "#create" do
65
+ describe "with a hash" do
66
+ it "returns a message of json type" do
67
+ Messagebus::Message.create({:benjamin => :franklins}).
68
+ raw_message.payload.should be_json
69
+ end
70
+ end
71
+
72
+ describe "with an object that responds to to_json" do
73
+ it "returns a message of json type" do
74
+ Messagebus::Message.create(Object.new).
75
+ raw_message.payload.should be_json
76
+ end
77
+ end
78
+
79
+ describe "with a binary string and binary arg" do
80
+ it "returns a message of binary type" do
81
+ Messagebus::Message.create("\xE5", nil, true).
82
+ raw_message.payload.should be_binary
83
+ end
84
+ end
85
+
86
+ describe "with a string" do
87
+ it "returns a message of string type" do
88
+ Messagebus::Message.create("benjamins").
89
+ raw_message.payload.should be_string
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,147 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'spec_helper')
2
+
3
+ describe Messagebus::Producer do
4
+ def start_test_producer(host_params, fake_stomp=stub(), options = {})
5
+ producer = Messagebus::Producer.new(host_params, options)
6
+ producer.stub!(:check_and_refresh_connection)
7
+ producer.should_receive(:start_server).with(host_params, "", "").and_return(fake_stomp)
8
+
9
+ producer
10
+ end
11
+
12
+ def test_publish_message(message, options={})
13
+ thrift_base64 = message.to_thrift_binary
14
+
15
+ fake_stomp = mock(Stomp::Client)
16
+ header_options = {}
17
+ if options[:header_options]
18
+ header_options = options[:header_options]
19
+ end
20
+
21
+ expected_header_options = header_options.merge(Messagebus::Producer::PUBLISH_HEADERS)
22
+
23
+ if options[:receipt]
24
+ receipt = stub(Stomp::Message)
25
+ receipt.should_receive(:command).and_return('RECEIPT')
26
+ fake_stomp.should_receive(:publish).with('jms.queue.testqueue', thrift_base64, expected_header_options).and_yield(receipt)
27
+ else
28
+ fake_stomp.should_receive(:publish).with('jms.queue.testqueue', thrift_base64, expected_header_options)
29
+ end
30
+
31
+ producer = start_test_producer(['localhost:61613'], fake_stomp, options)
32
+ # Start the producer.
33
+ producer.start
34
+
35
+ # Try publishing a simple string message.
36
+ producer.publish('jms.queue.testqueue', message, options[:header_options] || {}, options[:safe])
37
+
38
+ # also asserts that we can recreate the message from binary string receive.
39
+ message2 = Messagebus::Message.get_message_from_thrift_binary(thrift_base64)
40
+ end
41
+
42
+ before(:each) do
43
+ @msg_string_data1 = 'test data'
44
+
45
+ logger = mock(Logger, :info => true, :warn => true, :debug => true)
46
+ Messagebus::Client.stub!(:logger).and_return(logger)
47
+ end
48
+
49
+ describe "when starting a producer with multiple hosts" do
50
+ before do
51
+ @hosts = ["localhost:61613"]
52
+ @stomp = mock(Stomp)
53
+ end
54
+
55
+ it "starts a stomp server with multiple hosts" do
56
+ producer = Messagebus::Producer.new(@hosts)
57
+ producer.stub!(:refresh_server)
58
+ producer.should_receive(:start_server).with(@hosts, "", "").and_return(@stomp)
59
+ producer.start
60
+ end
61
+
62
+ it "starts a stomp server with one host" do
63
+ producer = Messagebus::Producer.new(@hosts.first)
64
+ producer.stub!(:refresh_server)
65
+ producer.should_receive(:start_server).with(@hosts, "", "").and_return(@stomp)
66
+ producer.start
67
+ end
68
+ end
69
+
70
+ describe "publish" do
71
+ it "check publish() with string message." do
72
+ message = Messagebus::Message.create(@msg_string_data1)
73
+ message2 = test_publish_message(message)
74
+ @msg_string_data1.should == message2.payload
75
+ end
76
+
77
+ it "check publish() with json message" do
78
+ msg_json_object = {:a => '1', :b => {:x => '2'}}
79
+
80
+ message = Messagebus::Message.create(msg_json_object)
81
+ message2 = test_publish_message(message)
82
+ json_string = msg_json_object.to_json
83
+ json_string.should == message2.payload
84
+ end
85
+
86
+ it "check publish() with binary message" do
87
+ msg_binary_data = "\xE5\xA5\xBD"
88
+
89
+ message = Messagebus::Message.create(msg_binary_data, nil, true)
90
+ message2 = test_publish_message(message)
91
+ msg_binary_data.should == message2.payload
92
+ end
93
+
94
+ context "deprecated dest_type parameter" do
95
+ before do
96
+ @fake_stomp = mock(Stomp::Client)
97
+ @producer = start_test_producer(['localhost:61613'], @fake_stomp)
98
+ @producer.start
99
+
100
+ @message = Messagebus::Message.create(@msg_string_data1)
101
+ end
102
+
103
+ it "supports not passing dest_type" do
104
+ @fake_stomp.should_receive(:publish).and_yield(stub.as_null_object)
105
+ @producer.publish('jms.queue.testqueue', @message)
106
+ end
107
+
108
+ it "supports 5 args with dest_type passed" do
109
+ @fake_stomp.should_receive(:publish).and_yield(stub.as_null_object)
110
+ @producer.publish('jms.queue.testqueue', 'topic', @message, {}, true)
111
+ end
112
+
113
+ it "supports 3 args with dest_type passed" do
114
+ @fake_stomp.should_receive(:publish).and_yield(stub.as_null_object)
115
+ @producer.publish('jms.queue.testqueue', 'topic', @message)
116
+ end
117
+ end
118
+ end
119
+
120
+ it "check publishSafe() with string message fail with timeout." do
121
+ message = Messagebus::Message.create(@msg_string_data1)
122
+
123
+ # Try publishing a simple string message, it should fail with timeout error.
124
+ Messagebus::Client.logger.should_receive(:error).with(/timeout while waiting for receipt/).twice
125
+ message2 = test_publish_message(message, :safe => true, :receipt_wait_timeout_ms => 250, :logger => @logger)
126
+ end
127
+
128
+ it "check publishSafe() with string message" do
129
+ message = Messagebus::Message.create(@msg_string_data1)
130
+ message2 = test_publish_message(message, :safe=>true, :receipt=>true, :receipt_wait_timeout_ms => 250)
131
+ @msg_string_data1.should == message2.payload
132
+ end
133
+
134
+ it "check publishSafe() with string message and scheduled message header" do
135
+ message = Messagebus::Message.create(@msg_string_data1)
136
+ message2 = test_publish_message(message, :safe=>true, :receipt=>true, :receipt_wait_timeout_ms => 250,
137
+ :header_options => {Messagebus::Producer::SCHEDULED_DELIVERY_TIME_MS_HEADER => '12345'})
138
+ @msg_string_data1.should == message2.payload
139
+ end
140
+
141
+ it "check publish() with string message and scheduled message header" do
142
+ message = Messagebus::Message.create(@msg_string_data1)
143
+ message2 = test_publish_message(message, :header_options => {Messagebus::Producer::SCHEDULED_DELIVERY_TIME_MS_HEADER => '12345'})
144
+ @msg_string_data1.should == message2.payload
145
+ end
146
+
147
+ end
@@ -0,0 +1,73 @@
1
+ require "spec_helper"
2
+
3
+ describe Messagebus::Swarm::Controller do
4
+ describe ".after_fork" do
5
+ class SampleWorker ; end
6
+
7
+ module AfterForkSpecHelpers
8
+ def temp_file_base
9
+ "after-fork-spec.tmp"
10
+ end
11
+
12
+ def create_temp_file(id)
13
+ `touch #{temp_file_base}#{id}`
14
+ end
15
+
16
+ def cleanup_temp_files
17
+ `rm #{temp_file_base}* > /dev/null 2>&1`
18
+ end
19
+
20
+ def temp_files_count
21
+ `ls -l #{temp_file_base}* | wc -l`.to_i
22
+ end
23
+ end
24
+ include AfterForkSpecHelpers
25
+
26
+ before do
27
+ Messagebus::Swarm::Drone.any_instance.stub(:processing_loop => nil)
28
+ end
29
+
30
+ around do |example|
31
+ cleanup_temp_files
32
+
33
+ original_logger = Messagebus::Swarm::Controller.swarm_control_logger
34
+ @logger = Logger.new(StringIO.new)
35
+ Messagebus::Swarm::Controller.swarm_control_logger = @logger
36
+
37
+ example.run
38
+
39
+ Messagebus::Swarm::Controller.swarm_control_logger = original_logger
40
+
41
+ cleanup_temp_files
42
+ end
43
+
44
+ it "executes the after_fork block once per drone started" do
45
+ config = {
46
+ :swarm_config => {:fork => true},
47
+ :workers => [
48
+ {
49
+ :destination => "jms.topic.SampleTopic",
50
+ :worker => "SampleWorker",
51
+ :ack_on_error => false,
52
+ :subscription_id => "sample-subscriber-id",
53
+ :drones => 4
54
+ }
55
+ ],
56
+ :clusters => [
57
+ {
58
+ "consumer_address" => "localhost:61613",
59
+ "destinations" => ["jms.topic.SampleTopic"]
60
+ }
61
+ ]
62
+ }
63
+
64
+ Messagebus::Swarm::Controller.after_fork do
65
+ create_temp_file(rand)
66
+ end
67
+
68
+ Messagebus::Swarm::Controller.start(config, @logger, "jms.topic.SampleTopic")
69
+
70
+ temp_files_count.should == config[:workers].first[:drones]
71
+ end
72
+ end
73
+ end