right_agent 0.5.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.
Files changed (147) hide show
  1. data/LICENSE +20 -0
  2. data/README.rdoc +78 -0
  3. data/Rakefile +86 -0
  4. data/lib/right_agent.rb +66 -0
  5. data/lib/right_agent/actor.rb +163 -0
  6. data/lib/right_agent/actor_registry.rb +76 -0
  7. data/lib/right_agent/actors/agent_manager.rb +189 -0
  8. data/lib/right_agent/agent.rb +735 -0
  9. data/lib/right_agent/agent_config.rb +403 -0
  10. data/lib/right_agent/agent_identity.rb +209 -0
  11. data/lib/right_agent/agent_tags_manager.rb +213 -0
  12. data/lib/right_agent/audit_formatter.rb +107 -0
  13. data/lib/right_agent/broker_client.rb +683 -0
  14. data/lib/right_agent/command.rb +30 -0
  15. data/lib/right_agent/command/agent_manager_commands.rb +134 -0
  16. data/lib/right_agent/command/command_client.rb +136 -0
  17. data/lib/right_agent/command/command_constants.rb +42 -0
  18. data/lib/right_agent/command/command_io.rb +128 -0
  19. data/lib/right_agent/command/command_parser.rb +87 -0
  20. data/lib/right_agent/command/command_runner.rb +105 -0
  21. data/lib/right_agent/command/command_serializer.rb +63 -0
  22. data/lib/right_agent/console.rb +65 -0
  23. data/lib/right_agent/core_payload_types.rb +42 -0
  24. data/lib/right_agent/core_payload_types/cookbook.rb +61 -0
  25. data/lib/right_agent/core_payload_types/cookbook_position.rb +46 -0
  26. data/lib/right_agent/core_payload_types/cookbook_repository.rb +116 -0
  27. data/lib/right_agent/core_payload_types/cookbook_sequence.rb +70 -0
  28. data/lib/right_agent/core_payload_types/dev_repositories.rb +90 -0
  29. data/lib/right_agent/core_payload_types/event_categories.rb +38 -0
  30. data/lib/right_agent/core_payload_types/executable_bundle.rb +138 -0
  31. data/lib/right_agent/core_payload_types/login_policy.rb +72 -0
  32. data/lib/right_agent/core_payload_types/login_user.rb +62 -0
  33. data/lib/right_agent/core_payload_types/planned_volume.rb +94 -0
  34. data/lib/right_agent/core_payload_types/recipe_instantiation.rb +60 -0
  35. data/lib/right_agent/core_payload_types/repositories_bundle.rb +50 -0
  36. data/lib/right_agent/core_payload_types/right_script_attachment.rb +95 -0
  37. data/lib/right_agent/core_payload_types/right_script_instantiation.rb +73 -0
  38. data/lib/right_agent/core_payload_types/secure_document.rb +66 -0
  39. data/lib/right_agent/core_payload_types/secure_document_location.rb +63 -0
  40. data/lib/right_agent/core_payload_types/software_repository_instantiation.rb +61 -0
  41. data/lib/right_agent/daemonize.rb +35 -0
  42. data/lib/right_agent/dispatcher.rb +348 -0
  43. data/lib/right_agent/enrollment_result.rb +217 -0
  44. data/lib/right_agent/exceptions.rb +30 -0
  45. data/lib/right_agent/ha_broker_client.rb +1278 -0
  46. data/lib/right_agent/idempotent_request.rb +140 -0
  47. data/lib/right_agent/log.rb +418 -0
  48. data/lib/right_agent/monkey_patches.rb +29 -0
  49. data/lib/right_agent/monkey_patches/amqp_patch.rb +274 -0
  50. data/lib/right_agent/monkey_patches/ruby_patch.rb +49 -0
  51. data/lib/right_agent/monkey_patches/ruby_patch/array_patch.rb +29 -0
  52. data/lib/right_agent/monkey_patches/ruby_patch/darwin_patch.rb +24 -0
  53. data/lib/right_agent/monkey_patches/ruby_patch/linux_patch.rb +24 -0
  54. data/lib/right_agent/monkey_patches/ruby_patch/linux_patch/file_patch.rb +30 -0
  55. data/lib/right_agent/monkey_patches/ruby_patch/object_patch.rb +49 -0
  56. data/lib/right_agent/monkey_patches/ruby_patch/singleton_patch.rb +46 -0
  57. data/lib/right_agent/monkey_patches/ruby_patch/string_patch.rb +107 -0
  58. data/lib/right_agent/monkey_patches/ruby_patch/windows_patch.rb +32 -0
  59. data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/file_patch.rb +90 -0
  60. data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/process_patch.rb +63 -0
  61. data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/stdio_patch.rb +27 -0
  62. data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/time_patch.rb +55 -0
  63. data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/win32ole_patch.rb +34 -0
  64. data/lib/right_agent/multiplexer.rb +91 -0
  65. data/lib/right_agent/operation_result.rb +270 -0
  66. data/lib/right_agent/packets.rb +637 -0
  67. data/lib/right_agent/payload_formatter.rb +104 -0
  68. data/lib/right_agent/pid_file.rb +159 -0
  69. data/lib/right_agent/platform.rb +319 -0
  70. data/lib/right_agent/platform/darwin.rb +227 -0
  71. data/lib/right_agent/platform/linux.rb +268 -0
  72. data/lib/right_agent/platform/windows.rb +1204 -0
  73. data/lib/right_agent/scripts/agent_controller.rb +522 -0
  74. data/lib/right_agent/scripts/agent_deployer.rb +379 -0
  75. data/lib/right_agent/scripts/common_parser.rb +153 -0
  76. data/lib/right_agent/scripts/log_level_manager.rb +193 -0
  77. data/lib/right_agent/scripts/stats_manager.rb +256 -0
  78. data/lib/right_agent/scripts/usage.rb +58 -0
  79. data/lib/right_agent/secure_identity.rb +92 -0
  80. data/lib/right_agent/security.rb +32 -0
  81. data/lib/right_agent/security/cached_certificate_store_proxy.rb +63 -0
  82. data/lib/right_agent/security/certificate.rb +102 -0
  83. data/lib/right_agent/security/certificate_cache.rb +89 -0
  84. data/lib/right_agent/security/distinguished_name.rb +56 -0
  85. data/lib/right_agent/security/encrypted_document.rb +84 -0
  86. data/lib/right_agent/security/rsa_key_pair.rb +76 -0
  87. data/lib/right_agent/security/signature.rb +86 -0
  88. data/lib/right_agent/security/static_certificate_store.rb +69 -0
  89. data/lib/right_agent/sender.rb +937 -0
  90. data/lib/right_agent/serialize.rb +29 -0
  91. data/lib/right_agent/serialize/message_pack.rb +102 -0
  92. data/lib/right_agent/serialize/secure_serializer.rb +131 -0
  93. data/lib/right_agent/serialize/secure_serializer_initializer.rb +47 -0
  94. data/lib/right_agent/serialize/serializable.rb +135 -0
  95. data/lib/right_agent/serialize/serializer.rb +149 -0
  96. data/lib/right_agent/stats_helper.rb +731 -0
  97. data/lib/right_agent/subprocess.rb +38 -0
  98. data/lib/right_agent/tracer.rb +124 -0
  99. data/right_agent.gemspec +60 -0
  100. data/spec/actor_registry_spec.rb +81 -0
  101. data/spec/actor_spec.rb +99 -0
  102. data/spec/agent_config_spec.rb +226 -0
  103. data/spec/agent_identity_spec.rb +75 -0
  104. data/spec/agent_spec.rb +571 -0
  105. data/spec/broker_client_spec.rb +961 -0
  106. data/spec/command/agent_manager_commands_spec.rb +51 -0
  107. data/spec/command/command_io_spec.rb +93 -0
  108. data/spec/command/command_parser_spec.rb +79 -0
  109. data/spec/command/command_runner_spec.rb +72 -0
  110. data/spec/command/command_serializer_spec.rb +51 -0
  111. data/spec/core_payload_types/dev_repositories_spec.rb +64 -0
  112. data/spec/core_payload_types/executable_bundle_spec.rb +59 -0
  113. data/spec/core_payload_types/login_user_spec.rb +98 -0
  114. data/spec/core_payload_types/right_script_attachment_spec.rb +65 -0
  115. data/spec/core_payload_types/spec_helper.rb +23 -0
  116. data/spec/dispatcher_spec.rb +372 -0
  117. data/spec/enrollment_result_spec.rb +53 -0
  118. data/spec/ha_broker_client_spec.rb +1673 -0
  119. data/spec/idempotent_request_spec.rb +136 -0
  120. data/spec/log_spec.rb +177 -0
  121. data/spec/monkey_patches/amqp_patch_spec.rb +100 -0
  122. data/spec/monkey_patches/eventmachine_spec.rb +62 -0
  123. data/spec/monkey_patches/string_patch_spec.rb +99 -0
  124. data/spec/multiplexer_spec.rb +48 -0
  125. data/spec/operation_result_spec.rb +171 -0
  126. data/spec/packets_spec.rb +418 -0
  127. data/spec/platform/platform_spec.rb +60 -0
  128. data/spec/results_mock.rb +45 -0
  129. data/spec/secure_identity_spec.rb +50 -0
  130. data/spec/security/cached_certificate_store_proxy_spec.rb +56 -0
  131. data/spec/security/certificate_cache_spec.rb +71 -0
  132. data/spec/security/certificate_spec.rb +49 -0
  133. data/spec/security/distinguished_name_spec.rb +46 -0
  134. data/spec/security/encrypted_document_spec.rb +55 -0
  135. data/spec/security/rsa_key_pair_spec.rb +55 -0
  136. data/spec/security/signature_spec.rb +66 -0
  137. data/spec/security/static_certificate_store_spec.rb +52 -0
  138. data/spec/sender_spec.rb +887 -0
  139. data/spec/serialize/message_pack_spec.rb +131 -0
  140. data/spec/serialize/secure_serializer_spec.rb +102 -0
  141. data/spec/serialize/serializable_spec.rb +90 -0
  142. data/spec/serialize/serializer_spec.rb +174 -0
  143. data/spec/spec.opts +2 -0
  144. data/spec/spec_helper.rb +77 -0
  145. data/spec/stats_helper_spec.rb +681 -0
  146. data/spec/tracer_spec.rb +114 -0
  147. metadata +320 -0
@@ -0,0 +1,66 @@
1
+ #
2
+ # Copyright (c) 2009-2011 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+
23
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
24
+
25
+ describe RightScale::Signature do
26
+
27
+ include RightScale::SpecHelper
28
+
29
+ before(:all) do
30
+ @test_data = "Test Data"
31
+ @cert, @key = issue_cert
32
+ @sig = RightScale::Signature.new(@test_data, @cert, @key)
33
+ end
34
+
35
+ it 'should create signed data' do
36
+ @sig.to_s.should_not be_empty
37
+ end
38
+
39
+ it 'should create signed data using either PEM or DER format' do
40
+ @sig.data(:pem).should_not be_empty
41
+ @sig.data(:der).should_not be_empty
42
+ end
43
+
44
+ it 'should verify the signature' do
45
+ cert2, key2 = issue_cert
46
+
47
+ @sig.should be_a_match(@cert)
48
+ @sig.should_not be_a_match(cert2)
49
+ end
50
+
51
+ it 'should load from serialized signature' do
52
+ sig2 = RightScale::Signature.from_data(@sig.data)
53
+ sig2.should_not be_nil
54
+ sig2.should be_a_match(@cert)
55
+ end
56
+
57
+ it 'should load from serialized signature using either PEM or DER format' do
58
+ sig2 = RightScale::Signature.from_data(@sig.data(:pem))
59
+ sig2.should_not be_nil
60
+ sig2.should be_a_match(@cert)
61
+ sig2 = RightScale::Signature.from_data(@sig.data(:der))
62
+ sig2.should_not be_nil
63
+ sig2.should be_a_match(@cert)
64
+ end
65
+
66
+ end
@@ -0,0 +1,52 @@
1
+ #
2
+ # Copyright (c) 2009-2011 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+
23
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
24
+
25
+ describe RightScale::StaticCertificateStore do
26
+
27
+ include RightScale::SpecHelper
28
+
29
+ before(:all) do
30
+ @signer, key = issue_cert
31
+ @recipient, key = issue_cert
32
+ @cert, @key = issue_cert
33
+ @store = RightScale::StaticCertificateStore.new(@signer, @recipient)
34
+ end
35
+
36
+ it 'should not raise when passed nil objects' do
37
+ res = nil
38
+ lambda { res = @store.get_signer(nil) }.should_not raise_error
39
+ res.should == [ @signer ]
40
+ lambda { res = @store.get_recipients(nil) }.should_not raise_error
41
+ res.should == [ @recipient ]
42
+ end
43
+
44
+ it 'should return signer certificates' do
45
+ @store.get_signer('anything').should == [ @signer ]
46
+ end
47
+
48
+ it 'should return recipient certificates' do
49
+ @store.get_recipients('anything').should == [ @recipient ]
50
+ end
51
+
52
+ end
@@ -0,0 +1,887 @@
1
+ #
2
+ # Copyright (c) 2009-2011 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+
23
+ require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
24
+
25
+ describe RightScale::Sender do
26
+
27
+ include FlexMock::ArgumentTypes
28
+
29
+ before(:each) do
30
+ @log = flexmock(RightScale::Log)
31
+ @log.should_receive(:error).by_default.and_return { |m| raise RightScale::Log.format(*m) }
32
+ @log.should_receive(:warning).by_default.and_return { |m| raise RightScale::Log.format(*m) }
33
+ @timer = flexmock("timer", :cancel => true).by_default
34
+ end
35
+
36
+ describe "when fetching the instance" do
37
+ before do
38
+ RightScale::Sender.class_eval do
39
+ if class_variable_defined?(:@@instance)
40
+ remove_class_variable(:@@instance)
41
+ end
42
+ end
43
+ end
44
+
45
+ it "should return nil when the instance is undefined" do
46
+ RightScale::Sender.instance.should == nil
47
+ end
48
+
49
+ it "should return the instance if defined" do
50
+ instance = flexmock
51
+ RightScale::Sender.class_eval do
52
+ @@instance = "instance"
53
+ end
54
+
55
+ RightScale::Sender.instance.should_not == nil
56
+ end
57
+ end
58
+
59
+ describe "when monitoring broker connectivity" do
60
+ before(:each) do
61
+ flexmock(EM).should_receive(:next_tick).and_yield.by_default
62
+ @broker = flexmock("Broker", :subscribe => true, :publish => ["broker"], :connected? => true,
63
+ :identity_parts => ["host", 123, 0, 0, nil]).by_default
64
+ @agent = flexmock("Agent", :identity => "agent", :broker => @broker, :options => {:ping_interval => 0}).by_default
65
+ end
66
+
67
+ it "should start inactivity timer at initialization time" do
68
+ @agent.should_receive(:options).and_return(:ping_interval => 1000)
69
+ flexmock(EM::Timer).should_receive(:new).with(1000, Proc).and_return(@timer).once
70
+ RightScale::Sender.new(@agent)
71
+ end
72
+
73
+ it "should not start inactivity timer at initialization time if ping disabled" do
74
+ flexmock(EM::Timer).should_receive(:new).never
75
+ RightScale::Sender.new(@agent)
76
+ end
77
+
78
+ it "should restart inactivity timer only if sufficient time has elapsed since last restart" do
79
+ @agent.should_receive(:options).and_return(:ping_interval => 1000)
80
+ flexmock(EM::Timer).should_receive(:new).with(1000, Proc).and_return(@timer).once
81
+ instance = RightScale::Sender.new(@agent)
82
+ flexmock(instance).should_receive(:restart_inactivity_timer).once
83
+ instance.message_received
84
+ instance.message_received
85
+ end
86
+
87
+ it "should check connectivity if the inactivity timer times out" do
88
+ @agent.should_receive(:options).and_return(:ping_interval => 1000)
89
+ flexmock(EM::Timer).should_receive(:new).and_return(@timer).once.by_default
90
+ RightScale::Sender.new(@agent)
91
+ instance = RightScale::Sender.instance
92
+ flexmock(EM::Timer).should_receive(:new).and_return(@timer).and_yield.once
93
+ flexmock(instance).should_receive(:check_connection).once
94
+ instance.message_received
95
+ end
96
+
97
+ it "should ignore messages received if ping disabled" do
98
+ @agent.should_receive(:options).and_return(:ping_interval => 0)
99
+ flexmock(EM::Timer).should_receive(:new).never
100
+ RightScale::Sender.new(@agent)
101
+ RightScale::Sender.instance.message_received
102
+ end
103
+
104
+ it "should log an exception if the connectivity check fails" do
105
+ @log.should_receive(:error).with(/Failed connectivity check/, Exception, :trace).once
106
+ @agent.should_receive(:options).and_return(:ping_interval => 1000)
107
+ flexmock(EM::Timer).should_receive(:new).and_return(@timer).once.by_default
108
+ RightScale::Sender.new(@agent)
109
+ instance = RightScale::Sender.instance
110
+ flexmock(EM::Timer).should_receive(:new).and_return(@timer).and_yield.once
111
+ flexmock(instance).should_receive(:check_connection).and_raise(Exception)
112
+ instance.message_received
113
+ end
114
+
115
+ it "should attempt to reconnect if mapper ping times out" do
116
+ @log.should_receive(:warning).with(/Mapper ping via broker/).once
117
+ @agent.should_receive(:options).and_return(:ping_interval => 1000)
118
+ broker_id = "rs-broker-localhost-5672"
119
+ @broker.should_receive(:identity_parts).with(broker_id).and_return(["localhost", 5672, 0, 0, nil]).once
120
+ @agent.should_receive(:connect).with("localhost", 5672, 0, 0, true).once
121
+ old_ping_timeout = RightScale::Sender::PING_TIMEOUT
122
+ begin
123
+ RightScale::Sender.const_set(:PING_TIMEOUT, 0.5)
124
+ EM.run do
125
+ EM.add_timer(1) { EM.stop }
126
+ RightScale::Sender.new(@agent)
127
+ instance = RightScale::Sender.instance
128
+ flexmock(instance).should_receive(:publish).with(RightScale::Request, nil).and_return([broker_id])
129
+ instance.__send__(:check_connection)
130
+ end
131
+ ensure
132
+ RightScale::Sender.const_set(:PING_TIMEOUT, old_ping_timeout)
133
+ end
134
+ end
135
+ end
136
+
137
+ describe "when making a push request" do
138
+ before(:each) do
139
+ @timer = flexmock("timer")
140
+ flexmock(EM::Timer).should_receive(:new).and_return(@timer)
141
+ @broker = flexmock("Broker", :subscribe => true, :publish => true).by_default
142
+ @agent = flexmock("Agent", :identity => "agent", :broker => @broker).by_default
143
+ @agent.should_receive(:options).and_return({}).by_default
144
+ RightScale::Sender.new(@agent)
145
+ @instance = RightScale::Sender.instance
146
+ @instance.initialize_offline_queue
147
+ end
148
+
149
+ it "should validate target" do
150
+ @broker.should_receive(:publish)
151
+ lambda { @instance.send_push('/foo/bar', nil) }.should be_true
152
+ lambda { @instance.send_push('/foo/bar', nil, "target") }.should be_true
153
+ lambda { @instance.send_push('/foo/bar', nil, {}) }.should be_true
154
+ lambda { @instance.send_push('/foo/bar', nil, :tags => "tags") }.should be_true
155
+ lambda { @instance.send_push('/foo/bar', nil, "tags" => "tags") }.should be_true
156
+ lambda { @instance.send_push('/foo/bar', nil, :tags => "tags", :scope => {:shard => 1}) }.should be_true
157
+ lambda { @instance.send_push('/foo/bar', nil, "scope" => {:shard => 1, "account" => 1}) }.should be_true
158
+ lambda { @instance.send_push('/foo/bar', nil, :scope => {:deployment => 1}) }.should be_true
159
+ lambda { @instance.send_push('/foo/bar', nil, :scope => {}) }.should be_true
160
+ lambda { @instance.send_push('/foo/bar', nil, :selector => :all) }.should be_true
161
+ lambda { @instance.send_push('/foo/bar', nil, "selector" => "any") }.should be_true
162
+ lambda { @instance.send_push('/foo/bar', nil, 1) }.should raise_error(ArgumentError)
163
+ lambda { @instance.send_push('/foo/bar', nil, []) }.should raise_error(ArgumentError)
164
+ lambda { @instance.send_push('/foo/bar', nil, :bogus => 1) }.should raise_error(ArgumentError)
165
+ lambda { @instance.send_push('/foo/bar', nil, :scope => 1) }.should raise_error(ArgumentError)
166
+ lambda { @instance.send_push('/foo/bar', nil, :scope => {:bogus => 1}) }.should raise_error(ArgumentError)
167
+ lambda { @instance.send_push('/foo/bar', nil, :selector => :bogus) }.should raise_error(ArgumentError)
168
+ end
169
+
170
+ it "should create a Push object" do
171
+ @broker.should_receive(:publish).with(hsh(:name => "request"), on do |push|
172
+ push.class.should == RightScale::Push
173
+ end, hsh(:persistent => false, :mandatory => true)).once
174
+ @instance.send_push('/welcome/aboard', 'iZac')
175
+ end
176
+
177
+ it "should set the correct target if specified" do
178
+ @broker.should_receive(:publish).with(hsh(:name => "request"), on do |push|
179
+ push.target.should == 'my-target'
180
+ end, hsh(:persistent => false, :mandatory => true)).once
181
+ @instance.send_push('/welcome/aboard', 'iZac', 'my-target')
182
+ end
183
+
184
+ it "should set the correct target selectors for fanout if specified" do
185
+ @broker.should_receive(:publish).with(hsh(:name => "request"), on do |push|
186
+ push.tags.should == ['tag']
187
+ push.selector.should == :all
188
+ push.scope.should == {:account => 123}
189
+ end, hsh(:persistent => false, :mandatory => true)).once
190
+ @instance.send_push('/welcome/aboard', 'iZac', :tags => ['tag'], :selector => :all, :scope => {:account => 123})
191
+ end
192
+
193
+ it "should default the target selector to any" do
194
+ @broker.should_receive(:publish).with(hsh(:name => "request"), on do |push|
195
+ push.tags.should == ['tag']
196
+ push.scope.should == {:account => 123}
197
+ end, hsh(:persistent => false, :mandatory => true)).once
198
+ @instance.send_push('/welcome/aboard', 'iZac', :tags => ['tag'], :scope => {:account => 123})
199
+ end
200
+
201
+ it "should set correct attributes on the push message" do
202
+ @broker.should_receive(:publish).with(hsh(:name => "request"), on do |push|
203
+ push.type.should == '/welcome/aboard'
204
+ push.token.should_not be_nil
205
+ push.persistent.should be_false
206
+ push.from.should == 'agent'
207
+ push.target.should be_nil
208
+ push.expires_at.should == 0
209
+ end, hsh(:persistent => false, :mandatory => true)).once
210
+ @instance.send_push('/welcome/aboard', 'iZac')
211
+ end
212
+
213
+ it 'should queue the push if in offline mode and :offline_queueing enabled' do
214
+ @agent.should_receive(:options).and_return({:offline_queueing => true})
215
+ RightScale::Sender.new(@agent)
216
+ @instance = RightScale::Sender.instance
217
+ @instance.initialize_offline_queue
218
+ @broker.should_receive(:publish).never
219
+ @instance.enable_offline_mode
220
+ @instance.instance_variable_get(:@queueing_mode).should == :offline
221
+ @instance.send_push('/welcome/aboard', 'iZac')
222
+ @instance.instance_variable_get(:@queue).size.should == 1
223
+ end
224
+
225
+ it "should store the response handler if given" do
226
+ response_handler = lambda {}
227
+ flexmock(RightScale::AgentIdentity).should_receive(:generate).and_return('abc').once
228
+ @instance.send_push('/welcome/aboard', 'iZac', &response_handler)
229
+ @instance.pending_requests['abc'][:response_handler].should == response_handler
230
+ end
231
+
232
+ it "should store the request receive time if there is a response handler" do
233
+ response_handler = lambda {}
234
+ flexmock(RightScale::AgentIdentity).should_receive(:generate).and_return('abc').once
235
+ flexmock(Time).should_receive(:now).and_return(Time.at(1000000)).by_default
236
+ @instance.request_age.should be_nil
237
+ @instance.send_push('/welcome/aboard', 'iZac', &response_handler)
238
+ @instance.pending_requests['abc'][:receive_time].should == Time.at(1000000)
239
+ flexmock(Time).should_receive(:now).and_return(Time.at(1000100))
240
+ @instance.request_age.should == 100
241
+ end
242
+ end
243
+
244
+ describe "when making a send_persistent_push request" do
245
+ before(:each) do
246
+ @timer = flexmock("timer")
247
+ flexmock(EM::Timer).should_receive(:new).and_return(@timer)
248
+ @broker = flexmock("Broker", :subscribe => true, :publish => true).by_default
249
+ @agent = flexmock("Agent", :identity => "agent", :broker => @broker, :options => {}).by_default
250
+ RightScale::Sender.new(@agent)
251
+ @instance = RightScale::Sender.instance
252
+ @instance.initialize_offline_queue
253
+ end
254
+
255
+ it "should create a Push object" do
256
+ @broker.should_receive(:publish).with(hsh(:name => "request"), on do |push|
257
+ push.class.should == RightScale::Push
258
+ end, hsh(:persistent => true, :mandatory => true)).once
259
+ @instance.send_persistent_push('/welcome/aboard', 'iZac')
260
+ end
261
+
262
+ it "should set correct attributes on the push message" do
263
+ @broker.should_receive(:publish).with(hsh(:name => "request"), on do |push|
264
+ push.type.should == '/welcome/aboard'
265
+ push.token.should_not be_nil
266
+ push.persistent.should be_true
267
+ push.from.should == 'agent'
268
+ push.target.should be_nil
269
+ push.expires_at.should == 0
270
+ end, hsh(:persistent => true, :mandatory => true)).once
271
+ @instance.send_persistent_push('/welcome/aboard', 'iZac')
272
+ end
273
+
274
+ it "should default the target selector to any" do
275
+ @broker.should_receive(:publish).with(hsh(:name => "request"), on do |push|
276
+ push.tags.should == ['tag']
277
+ push.scope.should == {:account => 123}
278
+ end, hsh(:persistent => true, :mandatory => true)).once
279
+ @instance.send_persistent_push('/welcome/aboard', 'iZac', :tags => ['tag'], :scope => {:account => 123})
280
+ end
281
+
282
+ it "should store the response handler if given" do
283
+ response_handler = lambda {}
284
+ flexmock(RightScale::AgentIdentity).should_receive(:generate).and_return('abc').once
285
+ @instance.send_persistent_push('/welcome/aboard', 'iZac', &response_handler)
286
+ @instance.pending_requests['abc'][:response_handler].should == response_handler
287
+ end
288
+
289
+ it "should store the request receive time if there is a response handler" do
290
+ response_handler = lambda {}
291
+ flexmock(RightScale::AgentIdentity).should_receive(:generate).and_return('abc').once
292
+ flexmock(Time).should_receive(:now).and_return(Time.at(1000000)).by_default
293
+ @instance.request_age.should be_nil
294
+ @instance.send_persistent_push('/welcome/aboard', 'iZac', &response_handler)
295
+ @instance.pending_requests['abc'][:receive_time].should == Time.at(1000000)
296
+ flexmock(Time).should_receive(:now).and_return(Time.at(1000100))
297
+ @instance.request_age.should == 100
298
+ end
299
+ end
300
+
301
+ describe "when making a send_retryable_request request" do
302
+ before(:each) do
303
+ @timer = flexmock("timer")
304
+ flexmock(EM::Timer).should_receive(:new).and_return(@timer).by_default
305
+ flexmock(EM).should_receive(:next_tick).and_yield.by_default
306
+ @broker_id = "broker"
307
+ @broker_ids = [@broker_id]
308
+ @broker = flexmock("Broker", :subscribe => true, :publish => @broker_ids, :connected? => true,
309
+ :identity_parts => ["host", 123, 0, 0, nil]).by_default
310
+ @agent = flexmock("Agent", :identity => "agent", :broker => @broker).by_default
311
+ @agent.should_receive(:options).and_return({:ping_interval => 0, :time_to_live => 100}).by_default
312
+ RightScale::Sender.new(@agent)
313
+ @instance = RightScale::Sender.instance
314
+ @instance.initialize_offline_queue
315
+ end
316
+
317
+ it "should validate target" do
318
+ @broker.should_receive(:publish)
319
+ lambda { @instance.send_retryable_request('/foo/bar', nil) }.should be_true
320
+ lambda { @instance.send_retryable_request('/foo/bar', nil, "target") }.should be_true
321
+ lambda { @instance.send_retryable_request('/foo/bar', nil, {}) }.should be_true
322
+ lambda { @instance.send_retryable_request('/foo/bar', nil, :tags => "tags") }.should be_true
323
+ lambda { @instance.send_retryable_request('/foo/bar', nil, "tags" => "tags") }.should be_true
324
+ lambda { @instance.send_retryable_request('/foo/bar', nil, :tags => "tags", :scope => {:shard => 1}) }.should be_true
325
+ lambda { @instance.send_retryable_request('/foo/bar', nil, "scope" => {:shard => 1, "account" => 1}) }.should be_true
326
+ lambda { @instance.send_retryable_request('/foo/bar', nil, :scope => {:deployment => 1}) }.should be_true
327
+ lambda { @instance.send_retryable_request('/foo/bar', nil, :scope => {}) }.should be_true
328
+ lambda { @instance.send_retryable_request('/foo/bar', nil, :selector => :all) }.should raise_error(ArgumentError)
329
+ lambda { @instance.send_retryable_request('/foo/bar', nil, 1) }.should raise_error(ArgumentError)
330
+ lambda { @instance.send_retryable_request('/foo/bar', nil, []) }.should raise_error(ArgumentError)
331
+ lambda { @instance.send_retryable_request('/foo/bar', nil, :bogus => 1) }.should raise_error(ArgumentError)
332
+ lambda { @instance.send_retryable_request('/foo/bar', nil, :scope => 1) }.should raise_error(ArgumentError)
333
+ lambda { @instance.send_retryable_request('/foo/bar', nil, :scope => {:bogus => 1}) }.should raise_error(ArgumentError)
334
+ lambda { @instance.send_retryable_request('/foo/bar', nil, :selector => :bogus) }.should raise_error(ArgumentError)
335
+ end
336
+
337
+ it "should create a Request object" do
338
+ @broker.should_receive(:publish).with(hsh(:name => "request"), on do |request|
339
+ request.class.should == RightScale::Request
340
+ end, hsh(:persistent => false, :mandatory => true)).once
341
+ @instance.send_retryable_request('/welcome/aboard', 'iZac') {|response|}
342
+ end
343
+
344
+ it "should process request in next tick to preserve pending request data integrity" do
345
+ flexmock(EM).should_receive(:next_tick).and_yield.once
346
+ @instance.send_retryable_request('/welcome/aboard', 'iZac') {|response|}
347
+ end
348
+
349
+ it "should set correct attributes on the request message" do
350
+ flexmock(Time).should_receive(:now).and_return(Time.at(1000000))
351
+ @broker.should_receive(:publish).with(hsh(:name => "request"), on do |request|
352
+ request.type.should == '/welcome/aboard'
353
+ request.token.should_not be_nil
354
+ request.persistent.should be_false
355
+ request.from.should == 'agent'
356
+ request.target.should be_nil
357
+ request.expires_at.should == 1000100
358
+ end, hsh(:persistent => false, :mandatory => true)).once
359
+ @instance.send_retryable_request('/welcome/aboard', 'iZac') {|response|}
360
+ end
361
+
362
+ it "should disable time-to-live if disabled in configuration" do
363
+ @agent.should_receive(:options).and_return({:ping_interval => 0, :time_to_live => 0})
364
+ RightScale::Sender.new(@agent)
365
+ @instance = RightScale::Sender.instance
366
+ @instance.initialize_offline_queue
367
+ flexmock(Time).should_receive(:now).and_return(Time.at(1000000))
368
+ @broker.should_receive(:publish).with(hsh(:name => "request"), on do |request|
369
+ request.expires_at.should == 0
370
+ end, hsh(:persistent => false, :mandatory => true)).once
371
+ @instance.send_retryable_request('/welcome/aboard', 'iZac') {|response|}
372
+ end
373
+
374
+ it "should set the correct target if specified" do
375
+ @broker.should_receive(:publish).with(hsh(:name => "request"), on do |request|
376
+ request.target.should == 'my-target'
377
+ end, hsh(:persistent => false, :mandatory => true)).once
378
+ @instance.send_retryable_request('/welcome/aboard', 'iZac', 'my-target') {|response|}
379
+ end
380
+
381
+ it "should set the correct target selectors if specified" do
382
+ @broker.should_receive(:publish).with(hsh(:name => "request"), on do |request|
383
+ request.tags.should == ['tag']
384
+ request.selector.should == :any
385
+ request.scope.should == {:account => 123}
386
+ end, hsh(:persistent => false, :mandatory => true)).once
387
+ @instance.send_retryable_request('/welcome/aboard', 'iZac', :tags => ['tag'], :scope => {:account => 123})
388
+ end
389
+
390
+ it "should set up for retrying the request if necessary by default" do
391
+ flexmock(@instance).should_receive(:publish_with_timeout_retry).once
392
+ @instance.send_retryable_request('/welcome/aboard', 'iZac', 'my-target') {|response|}
393
+ end
394
+
395
+ it "should store the response handler" do
396
+ response_handler = lambda {}
397
+ flexmock(RightScale::AgentIdentity).should_receive(:generate).and_return('abc').once
398
+ @instance.send_retryable_request('/welcome/aboard', 'iZac', &response_handler)
399
+ @instance.pending_requests['abc'][:response_handler].should == response_handler
400
+ end
401
+
402
+ it "should store the request receive time" do
403
+ flexmock(RightScale::AgentIdentity).should_receive(:generate).and_return('abc').once
404
+ flexmock(Time).should_receive(:now).and_return(Time.at(1000000)).by_default
405
+ @instance.request_age.should be_nil
406
+ @instance.send_retryable_request('/welcome/aboard', 'iZac')
407
+ @instance.pending_requests['abc'][:receive_time].should == Time.at(1000000)
408
+ flexmock(Time).should_receive(:now).and_return(Time.at(1000100))
409
+ @instance.request_age.should == 100
410
+ end
411
+
412
+ it 'should queue the request if in offline mode and :offline_queueing enabled' do
413
+ @agent.should_receive(:options).and_return({:offline_queueing => true})
414
+ RightScale::Sender.new(@agent)
415
+ @instance = RightScale::Sender.instance
416
+ @instance.initialize_offline_queue
417
+ @broker.should_receive(:publish).never
418
+ @instance.enable_offline_mode
419
+ @instance.instance_variable_get(:@queueing_mode).should == :offline
420
+ @instance.send_retryable_request('/welcome/aboard', 'iZac')
421
+ @instance.instance_variable_get(:@queue).size.should == 1
422
+ end
423
+
424
+ it "should dump the pending requests" do
425
+ flexmock(RightScale::AgentIdentity).should_receive(:generate).and_return('abc').once
426
+ flexmock(Time).should_receive(:now).and_return(Time.at(1000000))
427
+ @instance.send_retryable_request('/welcome/aboard', 'iZac')
428
+ @instance.dump_requests.should == ["#{Time.at(1000000).localtime} <abc>"]
429
+ end
430
+
431
+ describe "with retry" do
432
+ it "should convert value to nil if 0" do
433
+ @instance.__send__(:nil_if_zero, 0).should == nil
434
+ end
435
+
436
+ it "should not convert value to nil if not 0" do
437
+ @instance.__send__(:nil_if_zero, 1).should == 1
438
+ end
439
+
440
+ it "should leave value as nil if nil" do
441
+ @instance.__send__(:nil_if_zero, nil).should == nil
442
+ end
443
+
444
+ it "should not setup for retry if retry_timeout nil" do
445
+ flexmock(EM).should_receive(:add_timer).never
446
+ @agent.should_receive(:options).and_return({:retry_timeout => nil})
447
+ RightScale::Sender.new(@agent)
448
+ @instance = RightScale::Sender.instance
449
+ @broker.should_receive(:publish).once
450
+ @instance.send_retryable_request('/welcome/aboard', 'iZac') {|response|}
451
+ end
452
+
453
+ it "should not setup for retry if retry_interval nil" do
454
+ flexmock(EM).should_receive(:add_timer).never
455
+ @agent.should_receive(:options).and_return({:retry_interval => nil})
456
+ RightScale::Sender.new(@agent)
457
+ @instance = RightScale::Sender.instance
458
+ @broker.should_receive(:publish).once
459
+ @instance.send_retryable_request('/welcome/aboard', 'iZac') {|response|}
460
+ end
461
+
462
+ it "should not setup for retry if publish failed" do
463
+ flexmock(EM).should_receive(:add_timer).never
464
+ @agent.should_receive(:options).and_return({:retry_timeout => 60, :retry_interval => 60})
465
+ RightScale::Sender.new(@agent)
466
+ @instance = RightScale::Sender.instance
467
+ @broker.should_receive(:publish).and_return([]).once
468
+ @instance.send_retryable_request('/welcome/aboard', 'iZac') {|response|}
469
+ end
470
+
471
+ it "should setup for retry if retry_timeout and retry_interval not nil and publish successful" do
472
+ flexmock(EM).should_receive(:add_timer).with(60, any).once
473
+ @agent.should_receive(:options).and_return({:retry_timeout => 60, :retry_interval => 60})
474
+ RightScale::Sender.new(@agent)
475
+ @instance = RightScale::Sender.instance
476
+ @broker.should_receive(:publish).and_return(@broker_ids).once
477
+ @instance.send_retryable_request('/welcome/aboard', 'iZac') {|response|}
478
+ end
479
+
480
+ it "should adjust retry interval by recent request duration" do
481
+
482
+ end
483
+
484
+ it "should succeed after retrying once" do
485
+ EM.run do
486
+ token = 'abc'
487
+ result = RightScale::OperationResult.non_delivery(RightScale::OperationResult::RETRY_TIMEOUT)
488
+ flexmock(RightScale::AgentIdentity).should_receive(:generate).and_return(token).twice
489
+ @agent.should_receive(:options).and_return({:retry_timeout => 0.3, :retry_interval => 0.1})
490
+ RightScale::Sender.new(@agent)
491
+ @instance = RightScale::Sender.instance
492
+ flexmock(@instance).should_receive(:check_connection).once
493
+ @broker.should_receive(:publish).and_return(@broker_ids).twice
494
+ @instance.send_retryable_request('/welcome/aboard', 'iZac') do |response|
495
+ result = RightScale::OperationResult.from_results(response)
496
+ end
497
+ EM.add_timer(0.15) do
498
+ @instance.pending_requests.empty?.should be_false
499
+ @instance.handle_response(RightScale::Result.new(token, nil, {'from' => RightScale::OperationResult.success}, nil))
500
+ end
501
+ EM.add_timer(0.3) do
502
+ EM.stop
503
+ result.success?.should be_true
504
+ @instance.pending_requests.empty?.should be_true
505
+ end
506
+ end
507
+ end
508
+
509
+ it "should timeout after retrying twice" do
510
+ pending 'Too difficult to get timing right for Windows' if RightScale::Platform.windows?
511
+ EM.run do
512
+ result = RightScale::OperationResult.success
513
+ @log.should_receive(:warning).once
514
+ @agent.should_receive(:options).and_return({:retry_timeout => 0.6, :retry_interval => 0.1})
515
+ RightScale::Sender.new(@agent)
516
+ @instance = RightScale::Sender.instance
517
+ flexmock(@instance).should_receive(:check_connection).once
518
+ @broker.should_receive(:publish).and_return(@broker_ids).times(3)
519
+ @instance.send_retryable_request('/welcome/aboard', 'iZac') do |response|
520
+ result = RightScale::OperationResult.from_results(response)
521
+ end
522
+ @instance.pending_requests.empty?.should be_false
523
+ EM.add_timer(1) do
524
+ EM.stop
525
+ result.non_delivery?.should be_true
526
+ result.content.should == RightScale::OperationResult::RETRY_TIMEOUT
527
+ @instance.pending_requests.empty?.should be_true
528
+ end
529
+ end
530
+ end
531
+
532
+ it "should retry with same request expires_at value" do
533
+ EM.run do
534
+ token = 'abc'
535
+ expires_at = nil
536
+ flexmock(RightScale::AgentIdentity).should_receive(:generate).and_return(token).twice
537
+ @agent.should_receive(:options).and_return({:retry_timeout => 0.5, :retry_interval => 0.1})
538
+ RightScale::Sender.new(@agent)
539
+ @instance = RightScale::Sender.instance
540
+ flexmock(@instance).should_receive(:check_connection).once
541
+ @broker.should_receive(:publish).with(hsh(:name => "request"), on do |request|
542
+ request.expires_at.should == (expires_at ||= request.expires_at)
543
+ end, hsh(:persistent => false, :mandatory => true)).and_return(@broker_ids).twice
544
+ @instance.send_retryable_request('/welcome/aboard', 'iZac') {|response|}
545
+ EM.add_timer(0.2) { EM.stop }
546
+ end
547
+ end
548
+
549
+ describe "and checking connection status" do
550
+ before(:each) do
551
+ @broker_id = "broker"
552
+ @broker_ids = [@broker_id]
553
+ end
554
+
555
+ it "should not check connection if check already in progress" do
556
+ flexmock(EM::Timer).should_receive(:new).and_return(@timer).never
557
+ @instance.pending_ping = true
558
+ flexmock(@instance).should_receive(:publish).never
559
+ @instance.__send__(:check_connection, @broker_ids)
560
+ end
561
+
562
+ it "should publish ping to mapper" do
563
+ flexmock(EM::Timer).should_receive(:new).and_return(@timer).once
564
+ flexmock(@instance).should_receive(:publish).with(on { |request| request.type.should == "/mapper/ping" },
565
+ @broker_ids).and_return(@broker_ids).once
566
+ @instance.__send__(:check_connection, @broker_id)
567
+ @instance.pending_requests.size.should == 1
568
+ end
569
+
570
+ it "should not make any connection changes if receive ping response" do
571
+ flexmock(RightScale::AgentIdentity).should_receive(:generate).and_return('abc').once
572
+ @timer.should_receive(:cancel).once
573
+ flexmock(EM::Timer).should_receive(:new).and_return(@timer).once
574
+ flexmock(@instance).should_receive(:publish).and_return(@broker_ids).once
575
+ @instance.__send__(:check_connection, @broker_id)
576
+ @instance.pending_ping.should == @timer
577
+ @instance.pending_requests.size.should == 1
578
+ @instance.pending_requests['abc'][:response_handler].call(nil)
579
+ @instance.pending_ping.should == nil
580
+ end
581
+
582
+ it "should try to reconnect if ping times out" do
583
+ @log.should_receive(:warning).once
584
+ flexmock(EM::Timer).should_receive(:new).and_yield.once
585
+ flexmock(@agent).should_receive(:connect).once
586
+ @instance.__send__(:check_connection, @broker_id)
587
+ @instance.pending_ping.should == nil
588
+ end
589
+
590
+ it "should log error if attempt to reconnect fails" do
591
+ @log.should_receive(:warning).once
592
+ @log.should_receive(:error).with(/Failed to reconnect/, Exception, :trace).once
593
+ flexmock(@agent).should_receive(:connect).and_raise(Exception)
594
+ flexmock(EM::Timer).should_receive(:new).and_yield.once
595
+ @instance.__send__(:check_connection, @broker_id)
596
+ end
597
+ end
598
+ end
599
+ end
600
+
601
+ describe "when making a send_persistent_request" do
602
+ before(:each) do
603
+ @timer = flexmock("timer")
604
+ flexmock(EM::Timer).should_receive(:new).and_return(@timer).by_default
605
+ flexmock(EM).should_receive(:next_tick).and_yield.by_default
606
+ @broker_id = "broker"
607
+ @broker_ids = [@broker_id]
608
+ @broker = flexmock("Broker", :subscribe => true, :publish => @broker_ids, :connected? => true,
609
+ :identity_parts => ["host", 123, 0, 0, nil]).by_default
610
+ @agent = flexmock("Agent", :identity => "agent", :broker => @broker,
611
+ :options => {:ping_interval => 0, :time_to_live => 100}).by_default
612
+ RightScale::Sender.new(@agent)
613
+ @instance = RightScale::Sender.instance
614
+ @instance.initialize_offline_queue
615
+ end
616
+
617
+ it "should create a Request object" do
618
+ @broker.should_receive(:publish).with(hsh(:name => "request"), on do |request|
619
+ request.class.should == RightScale::Request
620
+ end, hsh(:persistent => true, :mandatory => true)).once
621
+ @instance.send_persistent_request('/welcome/aboard', 'iZac') {|response|}
622
+ end
623
+
624
+ it "should set correct attributes on the request message" do
625
+ flexmock(Time).should_receive(:now).and_return(Time.at(1000000))
626
+ @broker.should_receive(:publish).with(hsh(:name => "request"), on do |request|
627
+ request.type.should == '/welcome/aboard'
628
+ request.token.should_not be_nil
629
+ request.persistent.should be_true
630
+ request.from.should == 'agent'
631
+ request.target.should be_nil
632
+ request.expires_at.should == 0
633
+ end, hsh(:persistent => true, :mandatory => true)).once
634
+ @instance.send_persistent_request('/welcome/aboard', 'iZac') {|response|}
635
+ end
636
+
637
+ it "should set the correct target if specified" do
638
+ @broker.should_receive(:publish).with(hsh(:name => "request"), on do |request|
639
+ request.target.should == 'my-target'
640
+ end, hsh(:persistent => true, :mandatory => true)).once
641
+ @instance.send_persistent_request('/welcome/aboard', 'iZac', 'my-target') {|response|}
642
+ end
643
+
644
+ it "should set the correct target selectors if specified" do
645
+ @broker.should_receive(:publish).with(hsh(:name => "request"), on do |request|
646
+ request.tags.should == ['tag']
647
+ request.selector.should == :any
648
+ request.scope.should == {:account => 123}
649
+ end, hsh(:persistent => true, :mandatory => true)).once
650
+ @instance.send_persistent_request('/welcome/aboard', 'iZac', :tags => ['tag'], :scope => {:account => 123})
651
+ end
652
+
653
+ it "should not set up for retrying the request" do
654
+ flexmock(@instance).should_receive(:publish_with_timeout_retry).never
655
+ @instance.send_persistent_request('/welcome/aboard', 'iZac', 'my-target') {|response|}
656
+ end
657
+ end
658
+
659
+ describe "when handling a response" do
660
+ before(:each) do
661
+ flexmock(EM).should_receive(:next_tick).and_yield.by_default
662
+ flexmock(EM).should_receive(:defer).and_yield.by_default
663
+ @broker = flexmock("Broker", :subscribe => true, :publish => ["broker"], :connected? => true,
664
+ :identity_parts => ["host", 123, 0, 0, nil]).by_default
665
+ @agent = flexmock("Agent", :identity => "agent", :broker => @broker, :options => {:ping_interval => 0}).by_default
666
+ RightScale::Sender.new(@agent)
667
+ @instance = RightScale::Sender.instance
668
+ flexmock(RightScale::AgentIdentity, :generate => 'token1')
669
+ end
670
+
671
+ it "should deliver the response" do
672
+ @instance.send_retryable_request('/welcome/aboard', 'iZac') {|_|}
673
+ response = RightScale::Result.new('token1', 'to', RightScale::OperationResult.success, 'target1')
674
+ flexmock(@instance).should_receive(:deliver).with(response, Hash).once
675
+ @instance.handle_response(response)
676
+ end
677
+
678
+ it "should not deliver TARGET_NOT_CONNECTED and TTL_EXPIRATION responses for send_retryable_request" do
679
+ @instance.send_retryable_request('/welcome/aboard', 'iZac') {|_|}
680
+ flexmock(@instance).should_receive(:deliver).never
681
+ non_delivery = RightScale::OperationResult.non_delivery(RightScale::OperationResult::TARGET_NOT_CONNECTED)
682
+ response = RightScale::Result.new('token1', 'to', non_delivery, 'target1')
683
+ @instance.handle_response(response)
684
+ non_delivery = RightScale::OperationResult.non_delivery(RightScale::OperationResult::TTL_EXPIRATION)
685
+ response = RightScale::Result.new('token1', 'to', non_delivery, 'target1')
686
+ @instance.handle_response(response)
687
+ end
688
+
689
+ it "should record non-delivery regardless of whether there is a response handler" do
690
+ @instance.send_retryable_request('/welcome/aboard', 'iZac') {|_|}
691
+ non_delivery = RightScale::OperationResult.non_delivery(RightScale::OperationResult::NO_ROUTE_TO_TARGET)
692
+ response = RightScale::Result.new('token1', 'to', non_delivery, 'target1')
693
+ @instance.handle_response(response)
694
+ @instance.instance_variable_get(:@non_deliveries).total.should == 1
695
+ end
696
+
697
+ it "should log non-delivery if there is no response handler" do
698
+ @log.should_receive(:info).with(/Non-delivery of/).once
699
+ @instance.send_push('/welcome/aboard', 'iZac')
700
+ non_delivery = RightScale::OperationResult.non_delivery(RightScale::OperationResult::NO_ROUTE_TO_TARGET)
701
+ response = RightScale::Result.new('token1', 'to', non_delivery, 'target1')
702
+ @instance.handle_response(response)
703
+ end
704
+
705
+ it "should log a debug message if request no longer pending" do
706
+ @log.should_receive(:debug).with(/No pending request for response/).once
707
+ @instance.send_retryable_request('/welcome/aboard', 'iZac') {|_|}
708
+ @instance.pending_requests['token1'].should_not be_nil
709
+ @instance.pending_requests['token2'].should be_nil
710
+ response = RightScale::Result.new('token2', 'to', RightScale::OperationResult.success, 'target1')
711
+ @instance.handle_response(response)
712
+ end
713
+ end
714
+
715
+ describe "when delivering a response" do
716
+ before(:each) do
717
+ flexmock(EM).should_receive(:next_tick).and_yield.by_default
718
+ flexmock(EM).should_receive(:defer).and_yield.by_default
719
+ @broker = flexmock("Broker", :subscribe => true, :publish => ["broker"], :connected? => true,
720
+ :identity_parts => ["host", 123, 0, 0, nil]).by_default
721
+ @agent = flexmock("Agent", :identity => "agent", :broker => @broker, :options => {:ping_interval => 0}).by_default
722
+ RightScale::Sender.new(@agent)
723
+ @instance = RightScale::Sender.instance
724
+ flexmock(RightScale::AgentIdentity, :generate => 'token1')
725
+ end
726
+
727
+ it "should delete all associated pending requests" do
728
+ @instance.send_retryable_request('/welcome/aboard', 'iZac') {|_|}
729
+ @instance.pending_requests['token1'].should_not be_nil
730
+ response = RightScale::Result.new('token1', 'to', RightScale::OperationResult.success, 'target1')
731
+ @instance.handle_response(response)
732
+ @instance.pending_requests['token1'].should be_nil
733
+ end
734
+
735
+ it "should delete any associated retry requests" do
736
+ @instance.send_retryable_request('/welcome/aboard', 'iZac') {|_|}
737
+ @instance.pending_requests['token1'].should_not be_nil
738
+ @instance.pending_requests['token2'] = @instance.pending_requests['token1'].dup
739
+ @instance.pending_requests['token2'][:retry_parent] = 'token1'
740
+ response = RightScale::Result.new('token2', 'to', RightScale::OperationResult.success, 'target1')
741
+ @instance.handle_response(response)
742
+ @instance.pending_requests['token1'].should be_nil
743
+ @instance.pending_requests['token2'].should be_nil
744
+ end
745
+
746
+ it "should call the response handler" do
747
+ called = 0
748
+ @instance.send_retryable_request('/welcome/aboard', 'iZac') {|response| called += 1}
749
+ response = RightScale::Result.new('token1', 'to', RightScale::OperationResult.success, 'target1')
750
+ @instance.handle_response(response)
751
+ called.should == 1
752
+ end
753
+
754
+ it "should defer the response handler call if not single threaded" do
755
+ @agent.should_receive(:options).and_return({:single_threaded => false})
756
+ RightScale::Sender.new(@agent)
757
+ @instance = RightScale::Sender.instance
758
+ called = 0
759
+ @instance.send_retryable_request('/welcome/aboard', 'iZac') {|response| called += 1}
760
+ response = RightScale::Result.new('token1', 'to', RightScale::OperationResult.success, 'target1')
761
+ flexmock(EM).should_receive(:defer).and_yield.once
762
+ flexmock(EM).should_receive(:next_tick).never
763
+ @instance.handle_response(response)
764
+ called.should == 1
765
+ end
766
+
767
+ it "should not defer the response handler call if single threaded" do
768
+ @agent.should_receive(:options).and_return({:single_threaded => true})
769
+ RightScale::Sender.new(@agent)
770
+ @instance = RightScale::Sender.instance
771
+ called = 0
772
+ @instance.send_retryable_request('/welcome/aboard', 'iZac') {|response| called += 1}
773
+ response = RightScale::Result.new('token1', 'to', RightScale::OperationResult.success, 'target1')
774
+ flexmock(EM).should_receive(:next_tick).and_yield.once
775
+ flexmock(EM).should_receive(:defer).never
776
+ @instance.handle_response(response)
777
+ called.should == 1
778
+ end
779
+
780
+ it "should log an error if the response handler raises an exception but still delete pending request" do
781
+ @agent.should_receive(:options).and_return({:single_threaded => true})
782
+ @log.should_receive(:error).with(/Failed processing response/, Exception, :trace).once
783
+ @instance.send_retryable_request('/welcome/aboard', 'iZac') {|_| raise Exception}
784
+ @instance.pending_requests['token1'].should_not be_nil
785
+ response = RightScale::Result.new('token1', 'to', RightScale::OperationResult.success, 'target1')
786
+ @instance.handle_response(response)
787
+ @instance.pending_requests['token1'].should be_nil
788
+ end
789
+ end
790
+
791
+ describe "when use offline queueing" do
792
+ before(:each) do
793
+ @broker = flexmock("Broker", :subscribe => true, :publish => ["broker"], :connected? => true,
794
+ :identity_parts => ["host", 123, 0, 0, nil]).by_default
795
+ @agent = flexmock("Agent", :identity => "agent", :broker => @broker, :options => {:offline_queueing => true}).by_default
796
+ RightScale::Sender.new(@agent)
797
+ @instance = RightScale::Sender.instance
798
+ @instance.initialize_offline_queue
799
+ end
800
+
801
+ it 'should vote for restart after the maximum number of queued requests is reached' do
802
+ @instance.instance_variable_get(:@restart_vote_count).should == 0
803
+ EM.run do
804
+ @instance.enable_offline_mode
805
+ @instance.instance_variable_set(:@queue, ('*' * (RightScale::Sender::MAX_QUEUED_REQUESTS - 1)).split(//))
806
+ @instance.send_push('/dummy', 'payload')
807
+ EM.next_tick { EM.stop }
808
+ end
809
+ @instance.instance_variable_get(:@queue).size.should == RightScale::Sender::MAX_QUEUED_REQUESTS
810
+ @instance.instance_variable_get(:@restart_vote_count).should == 1
811
+ end
812
+
813
+ it 'should vote for restart after the threshold delay is reached' do
814
+ old_vote_delay = RightScale::Sender::RESTART_VOTE_DELAY
815
+ begin
816
+ RightScale::Sender.const_set(:RESTART_VOTE_DELAY, 0.1)
817
+ @instance.instance_variable_get(:@restart_vote_count).should == 0
818
+ EM.run do
819
+ @instance.enable_offline_mode
820
+ @instance.send_push('/dummy', 'payload')
821
+ EM.add_timer(0.5) { EM.stop }
822
+ end
823
+ @instance.instance_variable_get(:@restart_vote_count).should == 1
824
+ ensure
825
+ RightScale::Sender.const_set(:RESTART_VOTE_DELAY, old_vote_delay)
826
+ end
827
+ end
828
+
829
+ it 'should not flush queued requests until back online' do
830
+ old_flush_delay = RightScale::Sender::MAX_QUEUE_FLUSH_DELAY
831
+ begin
832
+ RightScale::Sender.const_set(:MAX_QUEUE_FLUSH_DELAY, 0.1)
833
+ EM.run do
834
+ @instance.enable_offline_mode
835
+ @instance.send_push('/dummy', 'payload')
836
+ EM.add_timer(0.5) { EM.stop }
837
+ end
838
+ ensure
839
+ RightScale::Sender.const_set(:MAX_QUEUE_FLUSH_DELAY, old_flush_delay)
840
+ end
841
+ end
842
+
843
+ it 'should flush queued requests once back online' do
844
+ old_flush_delay = RightScale::Sender::MAX_QUEUE_FLUSH_DELAY
845
+ @broker.should_receive(:publish).once.and_return { EM.stop }
846
+ begin
847
+ RightScale::Sender.const_set(:MAX_QUEUE_FLUSH_DELAY, 0.1)
848
+ EM.run do
849
+ @instance.enable_offline_mode
850
+ @instance.send_push('/dummy', 'payload')
851
+ @instance.disable_offline_mode
852
+ EM.add_timer(1) { EM.stop }
853
+ end
854
+ ensure
855
+ RightScale::Sender.const_set(:MAX_QUEUE_FLUSH_DELAY, old_flush_delay)
856
+ end
857
+ end
858
+
859
+ it 'should stop flushing when going back to offline mode' do
860
+ old_flush_delay = RightScale::Sender::MAX_QUEUE_FLUSH_DELAY
861
+ begin
862
+ RightScale::Sender.const_set(:MAX_QUEUE_FLUSH_DELAY, 0.1)
863
+ EM.run do
864
+ @instance.enable_offline_mode
865
+ @instance.send_push('/dummy', 'payload')
866
+ @instance.disable_offline_mode
867
+ @instance.instance_variable_get(:@flushing_queue).should be_true
868
+ @instance.instance_variable_get(:@stop_flushing_queue).should be_false
869
+ @instance.instance_variable_get(:@queueing_mode).should == :offline
870
+ @instance.enable_offline_mode
871
+ @instance.instance_variable_get(:@flushing_queue).should be_true
872
+ @instance.instance_variable_get(:@stop_flushing_queue).should be_true
873
+ @instance.instance_variable_get(:@queueing_mode).should == :offline
874
+ EM.add_timer(1) do
875
+ @instance.instance_variable_get(:@flushing_queue).should be_false
876
+ @instance.instance_variable_get(:@stop_flushing_queue).should be_false
877
+ @instance.instance_variable_get(:@queueing_mode).should == :offline
878
+ EM.stop
879
+ end
880
+ end
881
+ ensure
882
+ RightScale::Sender.const_set(:MAX_QUEUE_FLUSH_DELAY, old_flush_delay)
883
+ end
884
+ end
885
+ end
886
+
887
+ end