right_agent 2.0.7-x86-mingw32

Sign up to get free protection for your applications and to get access to all the features.
Files changed (176) hide show
  1. data/LICENSE +20 -0
  2. data/README.rdoc +82 -0
  3. data/Rakefile +113 -0
  4. data/lib/right_agent.rb +59 -0
  5. data/lib/right_agent/actor.rb +182 -0
  6. data/lib/right_agent/actor_registry.rb +76 -0
  7. data/lib/right_agent/actors/agent_manager.rb +232 -0
  8. data/lib/right_agent/agent.rb +1149 -0
  9. data/lib/right_agent/agent_config.rb +480 -0
  10. data/lib/right_agent/agent_identity.rb +210 -0
  11. data/lib/right_agent/agent_tag_manager.rb +237 -0
  12. data/lib/right_agent/audit_formatter.rb +107 -0
  13. data/lib/right_agent/clients.rb +31 -0
  14. data/lib/right_agent/clients/api_client.rb +383 -0
  15. data/lib/right_agent/clients/auth_client.rb +247 -0
  16. data/lib/right_agent/clients/balanced_http_client.rb +369 -0
  17. data/lib/right_agent/clients/base_retry_client.rb +495 -0
  18. data/lib/right_agent/clients/right_http_client.rb +279 -0
  19. data/lib/right_agent/clients/router_client.rb +493 -0
  20. data/lib/right_agent/command.rb +30 -0
  21. data/lib/right_agent/command/agent_manager_commands.rb +150 -0
  22. data/lib/right_agent/command/command_client.rb +136 -0
  23. data/lib/right_agent/command/command_constants.rb +33 -0
  24. data/lib/right_agent/command/command_io.rb +126 -0
  25. data/lib/right_agent/command/command_parser.rb +87 -0
  26. data/lib/right_agent/command/command_runner.rb +118 -0
  27. data/lib/right_agent/command/command_serializer.rb +63 -0
  28. data/lib/right_agent/connectivity_checker.rb +179 -0
  29. data/lib/right_agent/console.rb +65 -0
  30. data/lib/right_agent/core_payload_types.rb +44 -0
  31. data/lib/right_agent/core_payload_types/cookbook.rb +61 -0
  32. data/lib/right_agent/core_payload_types/cookbook_position.rb +46 -0
  33. data/lib/right_agent/core_payload_types/cookbook_repository.rb +116 -0
  34. data/lib/right_agent/core_payload_types/cookbook_sequence.rb +70 -0
  35. data/lib/right_agent/core_payload_types/dev_repositories.rb +100 -0
  36. data/lib/right_agent/core_payload_types/dev_repository.rb +76 -0
  37. data/lib/right_agent/core_payload_types/event_categories.rb +38 -0
  38. data/lib/right_agent/core_payload_types/executable_bundle.rb +130 -0
  39. data/lib/right_agent/core_payload_types/login_policy.rb +72 -0
  40. data/lib/right_agent/core_payload_types/login_user.rb +79 -0
  41. data/lib/right_agent/core_payload_types/planned_volume.rb +94 -0
  42. data/lib/right_agent/core_payload_types/recipe_instantiation.rb +73 -0
  43. data/lib/right_agent/core_payload_types/repositories_bundle.rb +50 -0
  44. data/lib/right_agent/core_payload_types/right_script_attachment.rb +95 -0
  45. data/lib/right_agent/core_payload_types/right_script_instantiation.rb +94 -0
  46. data/lib/right_agent/core_payload_types/runlist_policy.rb +44 -0
  47. data/lib/right_agent/core_payload_types/secure_document.rb +66 -0
  48. data/lib/right_agent/core_payload_types/secure_document_location.rb +63 -0
  49. data/lib/right_agent/core_payload_types/software_repository_instantiation.rb +61 -0
  50. data/lib/right_agent/daemonize.rb +35 -0
  51. data/lib/right_agent/dispatched_cache.rb +109 -0
  52. data/lib/right_agent/dispatcher.rb +272 -0
  53. data/lib/right_agent/enrollment_result.rb +221 -0
  54. data/lib/right_agent/exceptions.rb +87 -0
  55. data/lib/right_agent/history.rb +145 -0
  56. data/lib/right_agent/log.rb +460 -0
  57. data/lib/right_agent/minimal.rb +46 -0
  58. data/lib/right_agent/monkey_patches.rb +30 -0
  59. data/lib/right_agent/monkey_patches/ruby_patch.rb +55 -0
  60. data/lib/right_agent/monkey_patches/ruby_patch/array_patch.rb +29 -0
  61. data/lib/right_agent/monkey_patches/ruby_patch/darwin_patch.rb +24 -0
  62. data/lib/right_agent/monkey_patches/ruby_patch/linux_patch.rb +24 -0
  63. data/lib/right_agent/monkey_patches/ruby_patch/linux_patch/file_patch.rb +30 -0
  64. data/lib/right_agent/monkey_patches/ruby_patch/object_patch.rb +49 -0
  65. data/lib/right_agent/monkey_patches/ruby_patch/windows_patch.rb +32 -0
  66. data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/file_patch.rb +60 -0
  67. data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/process_patch.rb +63 -0
  68. data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/stdio_patch.rb +27 -0
  69. data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/time_patch.rb +55 -0
  70. data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/win32ole_patch.rb +34 -0
  71. data/lib/right_agent/multiplexer.rb +102 -0
  72. data/lib/right_agent/offline_handler.rb +270 -0
  73. data/lib/right_agent/operation_result.rb +300 -0
  74. data/lib/right_agent/packets.rb +673 -0
  75. data/lib/right_agent/payload_formatter.rb +104 -0
  76. data/lib/right_agent/pending_requests.rb +128 -0
  77. data/lib/right_agent/pid_file.rb +159 -0
  78. data/lib/right_agent/platform.rb +770 -0
  79. data/lib/right_agent/platform/unix/darwin/platform.rb +102 -0
  80. data/lib/right_agent/platform/unix/linux/platform.rb +305 -0
  81. data/lib/right_agent/platform/unix/platform.rb +226 -0
  82. data/lib/right_agent/platform/windows/mingw/platform.rb +447 -0
  83. data/lib/right_agent/platform/windows/mswin/platform.rb +236 -0
  84. data/lib/right_agent/platform/windows/platform.rb +1808 -0
  85. data/lib/right_agent/protocol_version_mixin.rb +69 -0
  86. data/lib/right_agent/retryable_request.rb +195 -0
  87. data/lib/right_agent/scripts/agent_controller.rb +543 -0
  88. data/lib/right_agent/scripts/agent_deployer.rb +400 -0
  89. data/lib/right_agent/scripts/common_parser.rb +160 -0
  90. data/lib/right_agent/scripts/log_level_manager.rb +192 -0
  91. data/lib/right_agent/scripts/stats_manager.rb +268 -0
  92. data/lib/right_agent/scripts/usage.rb +58 -0
  93. data/lib/right_agent/secure_identity.rb +92 -0
  94. data/lib/right_agent/security.rb +32 -0
  95. data/lib/right_agent/security/cached_certificate_store_proxy.rb +77 -0
  96. data/lib/right_agent/security/certificate.rb +102 -0
  97. data/lib/right_agent/security/certificate_cache.rb +89 -0
  98. data/lib/right_agent/security/distinguished_name.rb +56 -0
  99. data/lib/right_agent/security/encrypted_document.rb +83 -0
  100. data/lib/right_agent/security/rsa_key_pair.rb +76 -0
  101. data/lib/right_agent/security/signature.rb +86 -0
  102. data/lib/right_agent/security/static_certificate_store.rb +85 -0
  103. data/lib/right_agent/sender.rb +792 -0
  104. data/lib/right_agent/serialize.rb +29 -0
  105. data/lib/right_agent/serialize/message_pack.rb +107 -0
  106. data/lib/right_agent/serialize/secure_serializer.rb +151 -0
  107. data/lib/right_agent/serialize/secure_serializer_initializer.rb +47 -0
  108. data/lib/right_agent/serialize/serializable.rb +151 -0
  109. data/lib/right_agent/serialize/serializer.rb +159 -0
  110. data/lib/right_agent/subprocess.rb +38 -0
  111. data/lib/right_agent/tracer.rb +124 -0
  112. data/right_agent.gemspec +101 -0
  113. data/spec/actor_registry_spec.rb +80 -0
  114. data/spec/actor_spec.rb +162 -0
  115. data/spec/agent_config_spec.rb +235 -0
  116. data/spec/agent_identity_spec.rb +78 -0
  117. data/spec/agent_spec.rb +734 -0
  118. data/spec/agent_tag_manager_spec.rb +319 -0
  119. data/spec/clients/api_client_spec.rb +423 -0
  120. data/spec/clients/auth_client_spec.rb +272 -0
  121. data/spec/clients/balanced_http_client_spec.rb +576 -0
  122. data/spec/clients/base_retry_client_spec.rb +635 -0
  123. data/spec/clients/router_client_spec.rb +594 -0
  124. data/spec/clients/spec_helper.rb +111 -0
  125. data/spec/command/agent_manager_commands_spec.rb +51 -0
  126. data/spec/command/command_io_spec.rb +93 -0
  127. data/spec/command/command_parser_spec.rb +79 -0
  128. data/spec/command/command_runner_spec.rb +107 -0
  129. data/spec/command/command_serializer_spec.rb +51 -0
  130. data/spec/connectivity_checker_spec.rb +83 -0
  131. data/spec/core_payload_types/dev_repositories_spec.rb +64 -0
  132. data/spec/core_payload_types/dev_repository_spec.rb +33 -0
  133. data/spec/core_payload_types/executable_bundle_spec.rb +67 -0
  134. data/spec/core_payload_types/login_user_spec.rb +102 -0
  135. data/spec/core_payload_types/recipe_instantiation_spec.rb +81 -0
  136. data/spec/core_payload_types/right_script_attachment_spec.rb +65 -0
  137. data/spec/core_payload_types/right_script_instantiation_spec.rb +79 -0
  138. data/spec/core_payload_types/spec_helper.rb +23 -0
  139. data/spec/dispatched_cache_spec.rb +136 -0
  140. data/spec/dispatcher_spec.rb +324 -0
  141. data/spec/enrollment_result_spec.rb +53 -0
  142. data/spec/history_spec.rb +246 -0
  143. data/spec/log_spec.rb +192 -0
  144. data/spec/monkey_patches/eventmachine_spec.rb +62 -0
  145. data/spec/multiplexer_spec.rb +48 -0
  146. data/spec/offline_handler_spec.rb +340 -0
  147. data/spec/operation_result_spec.rb +208 -0
  148. data/spec/packets_spec.rb +461 -0
  149. data/spec/pending_requests_spec.rb +136 -0
  150. data/spec/platform/spec_helper.rb +216 -0
  151. data/spec/platform/unix/darwin/platform_spec.rb +181 -0
  152. data/spec/platform/unix/linux/platform_spec.rb +540 -0
  153. data/spec/platform/unix/spec_helper.rb +149 -0
  154. data/spec/platform/windows/mingw/platform_spec.rb +222 -0
  155. data/spec/platform/windows/mswin/platform_spec.rb +259 -0
  156. data/spec/platform/windows/spec_helper.rb +720 -0
  157. data/spec/retryable_request_spec.rb +306 -0
  158. data/spec/secure_identity_spec.rb +50 -0
  159. data/spec/security/cached_certificate_store_proxy_spec.rb +62 -0
  160. data/spec/security/certificate_cache_spec.rb +71 -0
  161. data/spec/security/certificate_spec.rb +49 -0
  162. data/spec/security/distinguished_name_spec.rb +46 -0
  163. data/spec/security/encrypted_document_spec.rb +55 -0
  164. data/spec/security/rsa_key_pair_spec.rb +55 -0
  165. data/spec/security/signature_spec.rb +66 -0
  166. data/spec/security/static_certificate_store_spec.rb +58 -0
  167. data/spec/sender_spec.rb +1045 -0
  168. data/spec/serialize/message_pack_spec.rb +131 -0
  169. data/spec/serialize/secure_serializer_spec.rb +132 -0
  170. data/spec/serialize/serializable_spec.rb +90 -0
  171. data/spec/serialize/serializer_spec.rb +197 -0
  172. data/spec/spec.opts +2 -0
  173. data/spec/spec.win32.opts +1 -0
  174. data/spec/spec_helper.rb +130 -0
  175. data/spec/tracer_spec.rb +114 -0
  176. metadata +447 -0
@@ -0,0 +1,49 @@
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::Certificate do
26
+
27
+ include RightScale::SpecHelper
28
+
29
+ before(:all) do
30
+ @certificate, key = issue_cert
31
+ end
32
+
33
+ it 'should save' do
34
+ filename = File.join(File.dirname(__FILE__), "cert.pem")
35
+ @certificate.save(filename)
36
+ File.size(filename).should be > 0
37
+ File.delete(filename)
38
+ end
39
+
40
+ it 'should load' do
41
+ filename = File.join(File.dirname(__FILE__), "cert.pem")
42
+ @certificate.save(filename)
43
+ cert = RightScale::Certificate.load(filename)
44
+ File.delete(filename)
45
+ cert.should_not be_nil
46
+ cert.data.should == @certificate.data
47
+ end
48
+
49
+ end
@@ -0,0 +1,46 @@
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::DistinguishedName do
26
+
27
+ before(:all) do
28
+ test_dn = { 'C' => 'US',
29
+ 'ST' => 'California',
30
+ 'L' => 'Santa Barbara',
31
+ 'O' => 'RightScale',
32
+ 'OU' => 'Certification Services',
33
+ 'CN' => 'rightscale.com/emailAddress=cert@rightscale.com' }
34
+ @dn = RightScale::DistinguishedName.new(test_dn)
35
+ end
36
+
37
+ it 'should convert to string and X509 DN' do
38
+ @dn.to_s.should_not be_nil
39
+ @dn.to_x509.should_not be_nil
40
+ end
41
+
42
+ it 'should correctly encode' do
43
+ @dn.to_s.should == @dn.to_x509.to_s
44
+ end
45
+
46
+ end
@@ -0,0 +1,55 @@
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::EncryptedDocument do
26
+
27
+ include RightScale::SpecHelper
28
+
29
+ before(:all) do
30
+ @test_data = "Test Data to Sign"
31
+ @cert, @key = issue_cert
32
+ @doc = RightScale::EncryptedDocument.new(@test_data, @cert)
33
+ end
34
+
35
+ it 'should create encrypted data' do
36
+ @doc.encrypted_data.should_not be_nil
37
+ end
38
+
39
+ it 'should create encrypted data using either PEM or DER format' do
40
+ @doc.encrypted_data(:pem).should_not be_nil
41
+ @doc.encrypted_data(:der).should_not be_nil
42
+ end
43
+
44
+ it 'should decrypt correctly' do
45
+ @doc.decrypted_data(@key, @cert).should == @test_data
46
+ end
47
+
48
+ it 'should load correctly with data in either PEM or DER format' do
49
+ @doc = RightScale::EncryptedDocument.from_data(@doc.encrypted_data(:pem))
50
+ @doc.decrypted_data(@key, @cert).should == @test_data
51
+ @doc = RightScale::EncryptedDocument.from_data(@doc.encrypted_data(:der))
52
+ @doc.decrypted_data(@key, @cert).should == @test_data
53
+ end
54
+
55
+ end
@@ -0,0 +1,55 @@
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::RsaKeyPair do
26
+
27
+ before(:all) do
28
+ @pair = RightScale::RsaKeyPair.new
29
+ end
30
+
31
+ it 'should create a private and a public keys' do
32
+ @pair.has_private?.should be_true
33
+ end
34
+
35
+ it 'should strip out private key in to_public' do
36
+ @pair.to_public.has_private?.should be_false
37
+ end
38
+
39
+ it 'should save' do
40
+ filename = File.join(File.dirname(__FILE__), "key.pem")
41
+ @pair.save(filename)
42
+ File.size(filename).should be > 0
43
+ File.delete(filename)
44
+ end
45
+
46
+ it 'should load' do
47
+ filename = File.join(File.dirname(__FILE__), "key.pem")
48
+ @pair.save(filename)
49
+ key = RightScale::RsaKeyPair.load(filename)
50
+ File.delete(filename)
51
+ key.should_not be_nil
52
+ key.data.should == @pair.data
53
+ end
54
+
55
+ end
@@ -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,58 @@
1
+ #
2
+ # Copyright (c) 2009-2013 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
+ @target, key = issue_cert
32
+ @cert, @key = issue_cert
33
+ @store = RightScale::StaticCertificateStore.new(@cert, @key, @signer, @target)
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_target(nil) }.should_not raise_error
41
+ res.should == [ @target ]
42
+ lambda { res = @store.get_receiver(nil) }.should_not raise_error
43
+ res.should == [ @cert, @key ]
44
+ end
45
+
46
+ it 'should return signer certificates' do
47
+ @store.get_signer('anything').should == [ @signer ]
48
+ end
49
+
50
+ it 'should return target certificates' do
51
+ @store.get_target('anything').should == [ @target ]
52
+ end
53
+
54
+ it 'should return certificate and key for decrypting' do
55
+ @store.get_receiver('anything').should == [ @cert, @key ]
56
+ end
57
+
58
+ end
@@ -0,0 +1,1045 @@
1
+ #
2
+ # Copyright (c) 2009-2013 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
+ context "access instance" do
30
+ before do
31
+ RightScale::Sender.class_eval do
32
+ remove_class_variable(:@@instance) if class_variable_defined?(:@@instance)
33
+ end
34
+ end
35
+
36
+ it "returns nil when the instance is undefined" do
37
+ RightScale::Sender.instance.should == nil
38
+ end
39
+
40
+ it "returns the instance if defined" do
41
+ RightScale::Sender.class_eval { @@instance = "instance" }
42
+ RightScale::Sender.instance.should_not == nil
43
+ end
44
+ end
45
+
46
+ context "use instance" do
47
+ # Create new sender with specified mode and options
48
+ # Also create mock agent and client for it with basic support for both modes
49
+ def create_sender(mode, options = {})
50
+ @broker_id = "rs-broker-1-1"
51
+ @broker_ids = [@broker_id]
52
+ @client = flexmock("client", :push => true, :request => true, :all => @broker_ids, :publish => @broker_ids).by_default
53
+ @agent_id = "rs-agent-1-1"
54
+ @agent = flexmock("agent", :identity => @agent_id, :client => @client, :mode => mode, :request_queue => "request",
55
+ :options => options).by_default
56
+ RightScale::Sender.new(@agent)
57
+ RightScale::Sender.instance
58
+ end
59
+
60
+ before(:each) do
61
+ @log = flexmock(RightScale::Log)
62
+ @log.should_receive(:error).by_default.and_return { |m| raise RightScale::Log.format(*m) }
63
+ @log.should_receive(:warning).by_default.and_return { |m| raise RightScale::Log.format(*m) }
64
+ @sender = create_sender(:http)
65
+ @token = "token"
66
+ @type = "/foo/bar"
67
+ @action = "bar"
68
+ @payload = {:pay => "load"}
69
+ @target = "target"
70
+ @callback = lambda { |_| }
71
+ end
72
+
73
+ context :initialize do
74
+ it "creates connectivity checker if mode is amqp" do
75
+ @sender = create_sender(:amqp)
76
+ @sender.connectivity_checker.should be_a(RightScale::ConnectivityChecker)
77
+ end
78
+ end
79
+
80
+ context "offline handling" do
81
+
82
+ context :initialize_offline_queue do
83
+ it "initializes offline handler" do
84
+ @sender = create_sender(:amqp, :offline_queueing => true)
85
+ flexmock(@sender.offline_handler).should_receive(:init).once
86
+ @sender.initialize_offline_queue
87
+ end
88
+
89
+ it "does nothing if offline queueing is disabled" do
90
+ flexmock(@sender.offline_handler).should_receive(:init).never
91
+ @sender.initialize_offline_queue
92
+ end
93
+ end
94
+
95
+ context :start_offline_queue do
96
+ it "starts offline handler" do
97
+ @sender = create_sender(:amqp, :offline_queueing => true)
98
+ flexmock(@sender.offline_handler).should_receive(:start).once
99
+ @sender.start_offline_queue
100
+ end
101
+
102
+ it "does nothing if offline queueing is disabled" do
103
+ flexmock(@sender.offline_handler).should_receive(:start).never
104
+ @sender.start_offline_queue
105
+ end
106
+ end
107
+
108
+ context :enable_offline_mode do
109
+ it "enables offline handler" do
110
+ @sender = create_sender(:amqp, :offline_queueing => true)
111
+ flexmock(@sender.offline_handler).should_receive(:enable).once
112
+ @sender.enable_offline_mode
113
+ end
114
+
115
+ it "does nothing if offline queueing is disabled" do
116
+ flexmock(@sender.offline_handler).should_receive(:enable).never
117
+ @sender.enable_offline_mode
118
+ end
119
+ end
120
+
121
+ context :disable_offline_mode do
122
+ it "initializes offline handler" do
123
+ @sender = create_sender(:amqp, :offline_queueing => true)
124
+ flexmock(@sender.offline_handler).should_receive(:disable).once
125
+ @sender.disable_offline_mode
126
+ end
127
+
128
+ it "does nothing if offline queueing is disabled" do
129
+ flexmock(@sender.offline_handler).should_receive(:disable).never
130
+ @sender.disable_offline_mode
131
+ end
132
+ end
133
+ end
134
+
135
+ context :send_push do
136
+ it "creates Push object" do
137
+ flexmock(RightSupport::Data::UUID, :generate => "random token")
138
+ flexmock(@sender).should_receive(:http_send).with(:send_push, nil, on { |a| a.class == RightScale::Push &&
139
+ a.type == @type && a.from == @agent_id && a.target.nil? && a.persistent == true && a.confirm.nil? &&
140
+ a.token == "random token" && a.expires_at == 0 }, Time, nil).once
141
+ @sender.send_push(@type).should be_true
142
+ end
143
+
144
+ it "stores payload" do
145
+ flexmock(@sender).should_receive(:http_send).with(:send_push, nil, on { |a| a.payload == @payload }, Time, nil).once
146
+ @sender.send_push(@type, @payload).should be_true
147
+ end
148
+
149
+ it "sets specific target using string" do
150
+ flexmock(@sender).should_receive(:http_send).with(:send_push, @target, on { |a| a.target == @target &&
151
+ a.selector == :any }, Time, nil).once
152
+ @sender.send_push(@type, nil, @target).should be_true
153
+ end
154
+
155
+ it "sets specific target using :agent_id" do
156
+ target = {:agent_id => @agent_id}
157
+ flexmock(@sender).should_receive(:http_send).with(:send_push, target, on { |a| a.target == @agent_id }, Time, nil).once
158
+ @sender.send_push(@type, nil, target).should be_true
159
+ end
160
+
161
+ it "sets target tags" do
162
+ tags = ["a:b=c"]
163
+ target = {:tags => tags}
164
+ flexmock(@sender).should_receive(:http_send).with(:send_push, target, on { |a| a.tags == tags &&
165
+ a.selector == :any }, Time, nil).once
166
+ @sender.send_push(@type, nil, target).should be_true
167
+ end
168
+
169
+ it "sets target scope" do
170
+ scope = {:shard => 1, :account => 123}
171
+ target = {:scope => scope}
172
+ flexmock(@sender).should_receive(:http_send).with(:send_push, target, on { |a| a.scope == scope &&
173
+ a.selector == :any }, Time, nil).once
174
+ @sender.send_push(@type, nil, target).should be_true
175
+ end
176
+
177
+ it "sets target selector" do
178
+ tags = ["a:b=c"]
179
+ target = {:tags => tags, :selector => :all}
180
+ flexmock(@sender).should_receive(:http_send).with(:send_push, target, on { |a| a.selector == :all }, Time, nil).once
181
+ @sender.send_push(@type, nil, target).should be_true
182
+ end
183
+
184
+ it "sets token" do
185
+ flexmock(@sender).should_receive(:http_send).with(:send_push, @target, on { |a| a.token == "token2" }, Time, nil).once
186
+ @sender.send_push(@type, nil, @target, "token2").should be_true
187
+ end
188
+
189
+ it "sets applies callback for returning response" do
190
+ flexmock(@sender).should_receive(:http_send).with(:send_push, nil, on { |a| a.confirm == true }, Time, Proc).once
191
+ @sender.send_push(@type) { |_| }.should be_true
192
+ end
193
+ end
194
+
195
+ context :send_request do
196
+ it "creates Request object" do
197
+ flexmock(RightSupport::Data::UUID, :generate => "random token")
198
+ flexmock(@sender).should_receive(:http_send).with(:send_request, nil, on { |a| a.class == RightScale::Request &&
199
+ a.type == @type && a.from == @agent_id && a.target.nil? && a.persistent.nil? && a.selector == :any &&
200
+ a.token == "random token" && a.expires_at == 0 }, Time, Proc).once
201
+ @sender.send_request(@type) { |_| }.should be_true
202
+ end
203
+
204
+ it "stores payload" do
205
+ flexmock(@sender).should_receive(:http_send).with(:send_request, nil, on { |a| a.payload == @payload }, Time, Proc)
206
+ @sender.send_request(@type, @payload) { |_| }.should be_true
207
+ end
208
+
209
+ it "sets specific target using string" do
210
+ flexmock(@sender).should_receive(:http_send).with(:send_request, @agent_id, on { |a| a.target == @agent_id &&
211
+ a.selector == :any }, Time, Proc).once
212
+ @sender.send_request(@type, nil, @agent_id) { |_| }.should be_true
213
+ end
214
+
215
+ it "sets specific target using :agent_id" do
216
+ target = {:agent_id => @agent_id}
217
+ flexmock(@sender).should_receive(:http_send).with(:send_request, target, on { |a| a.target == @agent_id }, Time, Proc).once
218
+ @sender.send_request(@type, nil, target) { |_| }.should be_true
219
+ end
220
+
221
+ it "sets target tags" do
222
+ tags = ["a:b=c"]
223
+ target = {:tags => tags}
224
+ flexmock(@sender).should_receive(:http_send).with(:send_request, target, on { |a| a.tags == tags &&
225
+ a.selector == :any }, Time, Proc).once
226
+ @sender.send_request(@type, nil, target) { |_| }.should be_true
227
+ end
228
+
229
+ it "sets target scope" do
230
+ scope = {:shard => 1, :account => 123}
231
+ target = {:scope => scope}
232
+ flexmock(@sender).should_receive(:http_send).with(:send_request, target, on { |a| a.scope == scope &&
233
+ a.selector == :any }, Time, Proc).once
234
+ @sender.send_request(@type, nil, target) { |_| }.should be_true
235
+ end
236
+
237
+ it "sets token" do
238
+ flexmock(@sender).should_receive(:http_send).with(:send_request, @target, on { |a| a.token == "token2" }, Time, Proc).once
239
+ @sender.send_request(@type, nil, @target, "token2") { |_| }.should be_true
240
+ end
241
+
242
+ it "sets expiration time if TTL enabled" do
243
+ @sender = create_sender(:http, :time_to_live => 60)
244
+ flexmock(@sender).should_receive(:http_send).with(:send_request, nil, on { |a| a.expires_at != 0 }, Time, Proc).once
245
+ @sender.send_request(@type) { |_| }.should be_true
246
+ end
247
+
248
+ it "does not allow fanout" do
249
+ lambda { @sender.send_request(@type, nil, :selector => :all) }.should raise_error(ArgumentError)
250
+ end
251
+
252
+ it "requires callback block" do
253
+ lambda { @sender.send_request(@type) }.should raise_error(ArgumentError)
254
+ end
255
+ end
256
+
257
+ context :build_and_send_packet do
258
+ [:http, :amqp].each do |mode|
259
+
260
+ context "when #{mode}" do
261
+ before(:each) do
262
+ @sender = create_sender(mode)
263
+ end
264
+
265
+ it "builds packet" do
266
+ packet = flexmock("packet", :type => @type, :token => @token, :selector => :any)
267
+ flexmock(@sender).should_receive(:build_packet).with(:send_push, @type, @payload, @target, @token, nil).and_return(packet).once
268
+ flexmock(@sender).should_receive("#{mode}_send".to_sym)
269
+ @sender.build_and_send_packet(:send_push, @type, @payload, @target, @token, callback = nil).should be_true
270
+ end
271
+
272
+ it "sends packet" do
273
+ flexmock(@sender).should_receive("#{mode}_send".to_sym).with(:send_push, @target,
274
+ on { |a| a.class == RightScale::Push }, Time, nil).once
275
+ @sender.build_and_send_packet(:send_push, @type, @payload, @target, @token, callback = nil).should be_true
276
+ end
277
+
278
+ it "ignores nil packet result when queueing" do
279
+ flexmock(@sender).should_receive(:build_packet).with(:send_push, @type, @payload, @target, @token, nil).and_return(nil).once
280
+ flexmock(@sender).should_receive("#{mode}_send".to_sym).never
281
+ @sender.build_and_send_packet(:send_push, @type, @payload, @target, @token, callback = nil).should be_true
282
+ end
283
+ end
284
+ end
285
+ end
286
+
287
+ context :build_packet do
288
+ [:send_push, :send_request].each do |kind|
289
+
290
+ context "when #{kind}" do
291
+ it "validates target" do
292
+ flexmock(@sender).should_receive(:validate_target).with(@target, kind == :send_push).once
293
+ @sender.build_packet(kind, @type, nil, @target, @token).should_not be_nil
294
+ end
295
+
296
+ it "submits request to offline handler and returns nil if queueing" do
297
+ flexmock(@sender).should_receive(:queueing?).and_return(true).once
298
+ @sender.build_packet(kind, @type, nil, @target, @token).should be_nil
299
+ end
300
+
301
+ it "generates a request token if none provided" do
302
+ flexmock(RightSupport::Data::UUID, :generate => "random token")
303
+ packet = @sender.build_packet(kind, @type, nil, @target, token = nil)
304
+ packet.token.should == "random token"
305
+ end
306
+
307
+ it "sets payload" do
308
+ packet = @sender.build_packet(kind, @type, @payload, @target, @token)
309
+ packet.payload.should == @payload
310
+ end
311
+
312
+ it "sets the packet from this agent" do
313
+ packet = @sender.build_packet(kind, @type, nil, @target, @token)
314
+ packet.from.should == @agent_id
315
+ end
316
+
317
+ it "sets target if target is not a hash" do
318
+ packet = @sender.build_packet(kind, @type, nil, @target, @token)
319
+ packet.target.should == @target
320
+ end
321
+
322
+ context "when target is a hash" do
323
+ it "sets agent ID" do
324
+ target = {:agent_id => @agent_id}
325
+ packet = @sender.build_packet(kind, @type, nil, target, @token)
326
+ packet.target.should == @agent_id
327
+ end
328
+
329
+ it "sets tags" do
330
+ tags = ["a:b=c"]
331
+ target = {:tags => tags}
332
+ packet = @sender.build_packet(kind, @type, nil, target, @token)
333
+ packet.tags.should == tags
334
+ packet.scope.should be_nil
335
+ end
336
+
337
+ it "sets scope" do
338
+ scope = {:shard => 1, :account => 123}
339
+ target = {:scope => scope}
340
+ packet = @sender.build_packet(kind, @type, nil, target, @token)
341
+ packet.tags.should == []
342
+ end
343
+ end
344
+
345
+ if kind == :send_push
346
+ it "defaults selector to :any" do
347
+ packet = @sender.build_packet(kind, @type, nil, @target, @token)
348
+ packet.selector.should == :any
349
+ end
350
+
351
+ it "sets selector" do
352
+ target = {:selector => :all}
353
+ packet = @sender.build_packet(kind, @type, nil, target, @token)
354
+ packet.selector.should == :all
355
+ end
356
+
357
+ it "sets persistent flag" do
358
+ packet = @sender.build_packet(kind, @type, nil, @target, @token)
359
+ packet.persistent.should be_true
360
+ end
361
+
362
+ it "enables confirm if callback provided" do
363
+ packet = @sender.build_packet(kind, @type, nil, @target, @token, @callback)
364
+ packet.confirm.should be_true
365
+ end
366
+
367
+ it "does not enable confirm if not callback provided" do
368
+ packet = @sender.build_packet(kind, @type, nil, @target, @token)
369
+ packet.confirm.should be_false
370
+ end
371
+
372
+ it "does not set expiration time" do
373
+ packet = @sender.build_packet(kind, @type, nil, @target, @token)
374
+ packet.expires_at.should == 0
375
+ end
376
+ else
377
+ it "always sets selector to :any" do
378
+ packet = @sender.build_packet(kind, @type, nil, @target, @token)
379
+ packet.selector.should == :any
380
+ end
381
+
382
+ it "sets expiration time to specified TTL" do
383
+ @sender = create_sender(:http, :time_to_live => 99)
384
+ now = Time.now
385
+ flexmock(Time).should_receive(:now).and_return(now)
386
+ packet = @sender.build_packet(kind, @type, nil, @target, @token)
387
+ packet.expires_at.should == (now + 99).to_i
388
+ end
389
+ end
390
+ end
391
+ end
392
+ end
393
+
394
+ context :handle_response do
395
+ before(:each) do
396
+ flexmock(RightSupport::Data::UUID, :generate => @token)
397
+ @received_at = Time.now
398
+ flexmock(Time).should_receive(:now).and_return(@received_at)
399
+ @callback = lambda { |_| }
400
+ @pending_request = RightScale::PendingRequest.new(:send_request, @received_at, @callback)
401
+ @sender.pending_requests[@token] = @pending_request
402
+ end
403
+
404
+ [:send_push, :send_request].each do |kind|
405
+ it "delivers the response for a #{kind}" do
406
+ @pending_request = RightScale::PendingRequest.new(kind, @received_at, @callback)
407
+ @sender.pending_requests[@token] = @pending_request
408
+ response = RightScale::Result.new(@token, "to", RightScale::OperationResult.success, @target)
409
+ flexmock(@sender).should_receive(:deliver_response).with(response, @pending_request).once
410
+ @sender.handle_response(response).should be_true
411
+ end
412
+ end
413
+
414
+ it "logs a debug message if request no longer pending" do
415
+ @sender.pending_requests.delete(@token)
416
+ @log.should_receive(:debug).with(/No pending request for response/).once
417
+ response = RightScale::Result.new(@token, "to", RightScale::OperationResult.success, @target)
418
+ @sender.handle_response(response)
419
+ end
420
+
421
+ it "ignores responses that are not a Result" do
422
+ flexmock(@sender).should_receive(:deliver_response).never
423
+ @sender.handle_response("response").should be_true
424
+ end
425
+
426
+ context "when non-delivery" do
427
+ before(:each) do
428
+ @reason = RightScale::OperationResult::TARGET_NOT_CONNECTED
429
+ non_delivery = RightScale::OperationResult.non_delivery(@reason)
430
+ @response = RightScale::Result.new(@token, "to", non_delivery, @target)
431
+ end
432
+
433
+ it "records non-delivery regardless of whether there is a pending request" do
434
+ @sender.pending_requests.delete(@token)
435
+ non_delivery = RightScale::OperationResult.non_delivery(RightScale::OperationResult::NO_ROUTE_TO_TARGET)
436
+ response = RightScale::Result.new(@token, "to", non_delivery, @target)
437
+ @sender.handle_response(response).should be_true
438
+ @sender.instance_variable_get(:@non_delivery_stats).total.should == 1
439
+ end
440
+
441
+ it "logs non-delivery if there is no pending request" do
442
+ @sender.pending_requests.delete(@token)
443
+ @log.should_receive(:info).with(/Non-delivery of/).once
444
+ non_delivery = RightScale::OperationResult.non_delivery(RightScale::OperationResult::NO_ROUTE_TO_TARGET)
445
+ response = RightScale::Result.new(@token, "to", non_delivery, @target)
446
+ @sender.handle_response(response).should be_true
447
+ end
448
+
449
+ context "for a Request" do
450
+
451
+ context "with target not connected" do
452
+ it "logs non-delivery but does not deliver it" do
453
+ @log.should_receive(:info).with(/Non-delivery of/).once
454
+ flexmock(@sender).should_receive(:deliver_response).never
455
+ @sender.handle_response(@response).should be_true
456
+ end
457
+
458
+ it "records non-delivery reason in pending request if for parent request" do
459
+ @log.should_receive(:info).with(/Non-delivery of/).once
460
+ flexmock(@sender).should_receive(:deliver_response).never
461
+ parent_token = "parent token"
462
+ parent_pending_request = RightScale::PendingRequest.new(:send_request, @received_at, @callback)
463
+ @sender.pending_requests[parent_token] = parent_pending_request
464
+ @pending_request.retry_parent_token = parent_token
465
+ @sender.handle_response(@response).should be_true
466
+ parent_pending_request.non_delivery.should == @reason
467
+ end
468
+
469
+ it "updates non-delivery reason if for retry request" do
470
+ flexmock(@sender).should_receive(:deliver_response).never
471
+ @sender.handle_response(@response).should be_true
472
+ @pending_request.non_delivery.should == @reason
473
+ end
474
+ end
475
+
476
+ context "with retry timeout and previous non-delivery" do
477
+ it "delivers response using stored non-delivery reason" do
478
+ @reason = RightScale::OperationResult::RETRY_TIMEOUT
479
+ @response.results = RightScale::OperationResult.non_delivery(@reason)
480
+ @pending_request.non_delivery = "other reason"
481
+ flexmock(@sender).should_receive(:deliver_response).with(on { |a| a.results.content == "other reason"},
482
+ @pending_request).once
483
+ @sender.handle_response(@response).should be_true
484
+ end
485
+ end
486
+
487
+ context "otherwise" do
488
+ it "delivers non-delivery response as is" do
489
+ @response.results = RightScale::OperationResult.non_delivery("other")
490
+ flexmock(@sender).should_receive(:deliver_response).with(on { |a| a.results.content == "other"},
491
+ RightScale::PendingRequest).once
492
+ @sender.handle_response(@response).should be_true
493
+ end
494
+ end
495
+ end
496
+ end
497
+ end
498
+
499
+ context :terminate do
500
+ it "terminates offline handler" do
501
+ flexmock(@sender.offline_handler).should_receive(:terminate).once
502
+ @sender.terminate
503
+ end
504
+
505
+ it "terminates connectivity checker if configured" do
506
+ @sender = create_sender(:amqp, :offline_queueing => true)
507
+ flexmock(@sender.connectivity_checker).should_receive(:terminate).once
508
+ @sender.terminate
509
+ end
510
+
511
+ it "returns number of pending requests and age of youngest request" do
512
+ receive_time = Time.now - 10
513
+ @sender.pending_requests[@token] = RightScale::PendingRequest.new(:send_request, receive_time, @callback)
514
+ @sender.terminate.should == [1, 10]
515
+ end
516
+ end
517
+
518
+ context :dump_requests do
519
+ it "returns array of unfinished non-push requests" do
520
+ time1 = Time.now
521
+ @sender.pending_requests["token1"] = RightScale::PendingRequest.new(:send_push, time1, @callback)
522
+ time2 = time1 + 10
523
+ @sender.pending_requests["token2"] = RightScale::PendingRequest.new(:send_request, time2, @callback)
524
+ @sender.dump_requests.should == ["#{time2.localtime} <token2>"]
525
+ end
526
+
527
+ it "returns requests in descending time order" do
528
+ time1 = Time.now
529
+ @sender.pending_requests["token1"] = RightScale::PendingRequest.new(:send_request, time1, @callback)
530
+ time2 = time1 + 10
531
+ @sender.pending_requests["token2"] = RightScale::PendingRequest.new(:send_request, time2, @callback)
532
+ @sender.dump_requests.should == ["#{time2.localtime} <token2>", "#{time1.localtime} <token1>"]
533
+ end
534
+
535
+ it "limits the number returned to 50" do
536
+ pending_request = RightScale::PendingRequest.new(:send_request, Time.now, @callback)
537
+ 55.times.each { |i| @sender.pending_requests["token#{i}"] = pending_request }
538
+ result = @sender.dump_requests
539
+ result.size.should == 51
540
+ result.last.should == "..."
541
+ end
542
+ end
543
+
544
+ context :validate_target do
545
+ it "should accept nil target" do
546
+ @sender.send(:validate_target, nil, true).should be_true
547
+ end
548
+
549
+ it "should accept named target" do
550
+ @sender.send(:validate_target, "name", true).should be_true
551
+ end
552
+
553
+ context "when target is a hash" do
554
+
555
+ context "and agent ID is specified" do
556
+ it "should not allow other keys" do
557
+ @sender.send(:validate_target, {:agent_id => @agent_id}, true).should be_true
558
+ lambda { @sender.send(:validate_target, {:agent_id => @agent_id, :tags => ["a:b=c"]}, true) }.should \
559
+ raise_error(ArgumentError, /Invalid target/)
560
+ end
561
+ end
562
+
563
+ context "and selector is allowed" do
564
+ it "should accept :all or :any selector" do
565
+ @sender.send(:validate_target, {:selector => :all}, true).should be_true
566
+ @sender.send(:validate_target, {"selector" => "any"}, true).should be_true
567
+ end
568
+
569
+ it "should reject values other than :all or :any" do
570
+ lambda { @sender.send(:validate_target, {:selector => :other}, true) }.
571
+ should raise_error(ArgumentError, /Invalid target selector/)
572
+ end
573
+ end
574
+
575
+ context "and selector is not allowed" do
576
+ it "should reject selector" do
577
+ lambda { @sender.send(:validate_target, {:selector => :all}, false) }.
578
+ should raise_error(ArgumentError, /Invalid target hash/)
579
+ end
580
+ end
581
+
582
+ context "and tags is specified" do
583
+ it "should accept tags" do
584
+ @sender.send(:validate_target, {:tags => []}, true).should be_true
585
+ @sender.send(:validate_target, {"tags" => ["tag"]}, true).should be_true
586
+ end
587
+
588
+ it "should reject non-array" do
589
+ lambda { @sender.send(:validate_target, {:tags => {}}, true) }.
590
+ should raise_error(ArgumentError, /Invalid target tags/)
591
+ end
592
+ end
593
+
594
+ context "and scope is specified" do
595
+ it "should accept account" do
596
+ @sender.send(:validate_target, {:scope => {:account => 1}}, true).should be_true
597
+ @sender.send(:validate_target, {"scope" => {"account" => 1}}, true).should be_true
598
+ end
599
+
600
+ it "should accept shard" do
601
+ @sender.send(:validate_target, {:scope => {:shard => 1}}, true).should be_true
602
+ @sender.send(:validate_target, {"scope" => {"shard" => 1}}, true).should be_true
603
+ end
604
+
605
+ it "should accept account and shard" do
606
+ @sender.send(:validate_target, {"scope" => {:shard => 1, "account" => 1}}, true).should be_true
607
+ end
608
+
609
+ it "should reject keys other than account and shard" do
610
+ target = {"scope" => {:shard => 1, "account" => 1, :other => 2}}
611
+ lambda { @sender.send(:validate_target, target, true) }.
612
+ should raise_error(ArgumentError, /Invalid target scope/)
613
+ end
614
+
615
+ it "should reject empty hash" do
616
+ lambda { @sender.send(:validate_target, {:scope => {}}, true) }.
617
+ should raise_error(ArgumentError, /Invalid target scope/)
618
+ end
619
+ end
620
+
621
+ context "and multiple are specified" do
622
+ it "should accept scope and tags" do
623
+ @sender.send(:validate_target, {:scope => {:shard => 1}, :tags => []}, true).should be_true
624
+ end
625
+
626
+ it "should accept scope, tags, and selector" do
627
+ target = {:scope => {:shard => 1}, :tags => ["tag"], :selector => :all}
628
+ @sender.send(:validate_target, target, true).should be_true
629
+ end
630
+
631
+ it "should reject selector if not allowed" do
632
+ target = {:scope => {:shard => 1}, :tags => ["tag"], :selector => :all}
633
+ lambda { @sender.send(:validate_target, target, false) }.
634
+ should raise_error(ArgumentError, /Invalid target hash/)
635
+ end
636
+ end
637
+
638
+ it "should reject keys other than selector, scope, and tags" do
639
+ target = {:scope => {:shard => 1}, :tags => [], :selector => :all, :other => 2}
640
+ lambda { @sender.send(:validate_target, target, true) }.
641
+ should raise_error(ArgumentError, /Invalid target hash/)
642
+ end
643
+
644
+ it "should reject empty hash" do
645
+ lambda { @sender.send(:validate_target, {}, true) }.
646
+ should raise_error(ArgumentError, /Invalid target hash/)
647
+ end
648
+
649
+ it "should reject value that is not nil, string, or hash" do
650
+ lambda { @sender.send(:validate_target, [], true) }.
651
+ should raise_error(ArgumentError, /Invalid target/)
652
+ end
653
+ end
654
+ end
655
+
656
+ context :http do
657
+
658
+ context :http_send do
659
+ before(:each) do
660
+ @received_at = Time.now
661
+ flexmock(Time).should_receive(:now).and_return(@received_at)
662
+ flexmock(RightSupport::Data::UUID).should_receive(:generate).and_return(@token).by_default
663
+ @packet = @sender.build_packet(:send_request, @type, @payload, @target, @token, @callback)
664
+ @response = nil
665
+ @callback = lambda { |response| @response = response }
666
+ end
667
+
668
+ it "sends request using configured client" do
669
+ @packet = @sender.build_packet(:send_push, @type, @payload, @target, @token, @callback)
670
+ @client.should_receive(:push).with(@type, @payload, @target, @token).and_return(nil).once
671
+ @sender.send(:http_send, :send_push, @target, @packet, @received_at, @callback).should be_true
672
+ end
673
+
674
+ it "responds with success result containing response" do
675
+ @client.should_receive(:request).with(@type, @payload, @target, @token).and_return("result").once
676
+ @sender.send(:http_send, :send_request, @target, @packet, @received_at, @callback).should be_true
677
+ @response.token.should == @token
678
+ @response.results.success?.should be_true
679
+ @response.results.content.should == "result"
680
+ @response.from.should == @target
681
+ @response.received_at.should == @received_at.to_f
682
+ end
683
+
684
+ it "responds with success result when response is nil" do
685
+ @client.should_receive(:request).with(@type, @payload, @target, @token).and_return(nil).once
686
+ @sender.send(:http_send, :send_request, @target, @packet, @received_at, @callback).should be_true
687
+ @response.token.should == @token
688
+ @response.results.success?.should be_true
689
+ @response.results.content.should be_nil
690
+ @response.from.should == @target
691
+ @response.received_at.should == @received_at.to_f
692
+ end
693
+
694
+ it "responds asynchronously if requested" do
695
+ @sender = create_sender(:http, :async_response => true)
696
+ flexmock(EM).should_receive(:next_tick).and_yield.once
697
+ @client.should_receive(:request).with(@type, @payload, @target, @token).and_return(nil).once
698
+ @sender.send(:http_send, :send_request, @target, @packet, @received_at, @callback).should be_true
699
+ @response.token.should == @token
700
+ @response.results.success?.should be_true
701
+ @response.results.content.should be_nil
702
+ @response.from.should == @target
703
+ @response.received_at.should == @received_at.to_f
704
+ end
705
+
706
+ context "when fails" do
707
+ context "with connectivity error" do
708
+ it "queues request if queueing and does not respond" do
709
+ @sender = create_sender(:http, :offline_queueing => true)
710
+ @sender.initialize_offline_queue
711
+ @sender.enable_offline_mode
712
+ @client.should_receive(:request).and_raise(RightScale::Exceptions::ConnectivityFailure, "disconnected").once
713
+ flexmock(@sender.offline_handler).should_receive(:queue_request).with(:send_request, @type,
714
+ @payload, @target, @callback).once
715
+ flexmock(@sender).should_receive(:handle_response).never
716
+ @sender.send(:http_send, :send_request, @target, @packet, @received_at, @callback).should be_true
717
+ end
718
+
719
+ it "responds with retry result if not queueing" do
720
+ @client.should_receive(:request).and_raise(RightScale::Exceptions::ConnectivityFailure, "Server not responding").once
721
+ @sender.send(:http_send, :send_request, @target, @packet, @received_at, @callback).should be_true
722
+ @response.results.retry?.should be_true
723
+ @response.results.content.should == "Server not responding"
724
+ end
725
+ end
726
+
727
+ it "responds with retry result if retryable error" do
728
+ @client.should_receive(:request).and_raise(RightScale::Exceptions::RetryableError, "try again").once
729
+ @sender.send(:http_send, :send_request, @target, @packet, @received_at, @callback).should be_true
730
+ @response.results.retry?.should be_true
731
+ @response.results.content.should == "try again"
732
+ end
733
+
734
+ it "responds with error result if internal error" do
735
+ @client.should_receive(:request).and_raise(RightScale::Exceptions::InternalServerError.new("unprocessable", "Router")).once
736
+ @sender.send(:http_send, :send_request, @target, @packet, @received_at, @callback).should be_true
737
+ @response.results.error?.should be_true
738
+ @response.results.content.should == "Router internal error"
739
+ end
740
+
741
+ it "does not respond to request if terminating" do
742
+ @client.should_receive(:request).and_raise(RightScale::Exceptions::Terminating, "going down").once
743
+ flexmock(@sender).should_receive(:handle_response).never
744
+ @sender.send(:http_send, :send_request, @target, @packet, @received_at, @callback).should be_true
745
+ end
746
+
747
+ it "responds with error result if HTTP error" do
748
+ @client.should_receive(:request).and_raise(RestExceptionMock.new(400, "bad data")).once
749
+ @sender.send(:http_send, :send_request, @target, @packet, @received_at, @callback).should be_true
750
+ @response.results.error?.should be_true
751
+ @response.results.content.should == "400 Bad Request: bad data"
752
+ end
753
+
754
+ it "responds with error result if unexpected error" do
755
+ @log.should_receive(:error).with(/Failed to send/, StandardError, :trace).once
756
+ @client.should_receive(:request).and_raise(StandardError, "unexpected").once
757
+ @sender.send(:http_send, :send_request, @target, @packet, @received_at, @callback).should be_true
758
+ @response.results.error?.should be_true
759
+ @response.results.content.should == "Agent agent internal error"
760
+ end
761
+ end
762
+ end
763
+ end
764
+
765
+ context :amqp do
766
+ before(:each) do
767
+ @sender = create_sender(:amqp)
768
+ flexmock(RightSupport::Data::UUID).should_receive(:generate).and_return(@token).by_default
769
+ @packet = @sender.build_packet(:send_push, @type, @payload, @target, @token, nil)
770
+ @received_at = Time.now
771
+ flexmock(Time).should_receive(:now).and_return(@received_at)
772
+ @response = nil
773
+ @callback = lambda { |response| @response = response }
774
+ end
775
+
776
+ context :amqp_send do
777
+ it "stores pending request for use in responding if callback specified" do
778
+ @packet = @sender.build_packet(:send_push, @type, @payload, @target, @token, @callback)
779
+ flexmock(@sender).should_receive(:amqp_send_once)
780
+ @sender.send(:amqp_send, :send_push, @target, @packet, @received_at, @callback).should be_true
781
+ @sender.pending_requests[@token].should_not be_nil
782
+ end
783
+
784
+ it "does not store pending request if callback not specified" do
785
+ flexmock(@sender).should_receive(:amqp_send_once)
786
+ @sender.send(:amqp_send, :send_push, @target, @packet, @received_at, nil).should be_true
787
+ @sender.pending_requests[@token].should be_nil
788
+ end
789
+
790
+ it "publishes without retry capability if send_push" do
791
+ flexmock(@sender).should_receive(:amqp_send_once).with(@packet).once
792
+ @sender.send(:amqp_send, :send_push, @target, @packet, @received_at, nil).should be_true
793
+ end
794
+
795
+ it "publishes with retry capability if send_request" do
796
+ @packet = @sender.build_packet(:send_request, @type, @payload, @target, @token, @callback)
797
+ flexmock(@sender).should_receive(:amqp_send_retry).with(@packet, @token).once
798
+ @sender.send(:amqp_send, :send_request, @target, @packet, @received_at, @callback).should be_true
799
+ end
800
+
801
+ context "when fails" do
802
+
803
+ context "with offline error" do
804
+ it "submits request to offline handler if queuing" do
805
+ @sender = create_sender(:amqp, :offline_queueing => true)
806
+ @sender.initialize_offline_queue
807
+ @sender.enable_offline_mode
808
+ flexmock(@sender).should_receive(:amqp_send_once).and_raise(RightScale::Sender::TemporarilyOffline).once
809
+ @sender.send(:amqp_send, :send_push, @target, @packet, @received_at, nil).should be_true
810
+ @sender.offline_handler.queue.size.should == 1
811
+ @sender.pending_requests[@token].should be_nil
812
+ end
813
+
814
+ it "responds with retry result if not queueing" do
815
+ flexmock(@sender).should_receive(:amqp_send_once).and_raise(RightScale::Sender::TemporarilyOffline).once
816
+ @sender.send(:amqp_send, :send_push, @target, @packet, @received_at, @callback).should be_true
817
+ @sender.offline_handler.queue.size.should == 0
818
+ @sender.pending_requests[@token].should_not be_nil # because send_push does not get deleted when deliver
819
+ @response.results.retry?.should be_true
820
+ @response.results.content.should == "lost RightNet connectivity"
821
+ end
822
+ end
823
+
824
+ it "responds with non-delivery result if send failure" do
825
+ flexmock(@sender).should_receive(:amqp_send_once).and_raise(RightScale::Sender::SendFailure).once
826
+ @sender.send(:amqp_send, :send_push, @target, @packet, @received_at, @callback).should be_true
827
+ @sender.pending_requests[@token].should_not be_nil # because send_push does not get deleted when deliver
828
+ @response.results.non_delivery?.should be_true
829
+ @response.results.content.should == "send failed unexpectedly"
830
+ end
831
+ end
832
+ end
833
+
834
+ context :amqp_send_once do
835
+ it "publishes request to request queue" do
836
+ @client.should_receive(:publish).with(hsh(:name => "request"), @packet, hsh(:persistent => true,
837
+ :mandatory => true, :broker_ids => nil)).and_return(@broker_ids).once
838
+ @sender.send(:amqp_send_once, @packet, @broker_ids).should == @broker_ids
839
+ end
840
+
841
+ context "when fails" do
842
+ it "raises TemporarilyOffline if no connected brokers" do
843
+ @log.should_receive(:error).with(/Failed to publish/, RightAMQP::HABrokerClient::NoConnectedBrokers).once
844
+ @client.should_receive(:publish).and_raise(RightAMQP::HABrokerClient::NoConnectedBrokers).once
845
+ lambda { @sender.send(:amqp_send_once, @packet) }.should raise_error(RightScale::Sender::TemporarilyOffline)
846
+ end
847
+
848
+ it "raises SendFailure if unexpected exception" do
849
+ @log.should_receive(:error).with(/Failed to publish/, StandardError, :trace).once
850
+ @client.should_receive(:publish).and_raise(StandardError, "unexpected").once
851
+ lambda { @sender.send(:amqp_send_once, @packet) }.should raise_error(RightScale::Sender::SendFailure)
852
+ end
853
+ end
854
+ end
855
+
856
+ context :amqp_send_retry do
857
+ before(:each) do
858
+ flexmock(RightSupport::Data::UUID).should_receive(:generate).and_return("retry token")
859
+ @packet = @sender.build_packet(:send_request, @type, @payload, @target, @token, @callback)
860
+ end
861
+
862
+ it "publishes request to request queue" do
863
+ @client.should_receive(:publish).with(hsh(:name => "request"), @packet, hsh(:persistent => nil,
864
+ :mandatory => true, :broker_ids => nil)).and_return(@broker_ids).once
865
+ @sender.send(:amqp_send_retry, @packet, @token).should be_true
866
+ end
867
+
868
+ it "does not rescue if publish fails" do
869
+ @log.should_receive(:error).with(/Failed to publish request/, RightAMQP::HABrokerClient::NoConnectedBrokers).once
870
+ @client.should_receive(:publish).and_raise(RightAMQP::HABrokerClient::NoConnectedBrokers).once
871
+ lambda { @sender.send(:amqp_send_retry, @packet, @token) }.should raise_error(RightScale::Sender::TemporarilyOffline)
872
+ end
873
+
874
+ it "does not retry if retry timeout not set" do
875
+ @sender = create_sender(:amqp, :retry_interval => 10)
876
+ @client.should_receive(:publish).once
877
+ flexmock(EM).should_receive(:add_timer).never
878
+ @sender.send(:amqp_send_retry, @packet, @token).should be_true
879
+ end
880
+
881
+ it "does not retry if retry interval not set" do
882
+ @sender = create_sender(:amqp, :retry_timeout => 60)
883
+ @client.should_receive(:publish).once
884
+ flexmock(EM).should_receive(:add_timer).never
885
+ @sender.send(:amqp_send_retry, @packet, @token).should be_true
886
+ end
887
+
888
+ context "when retry enabled" do
889
+ it "uses timer to wait for a response" do
890
+ @sender = create_sender(:amqp, :retry_timeout => 0.3, :retry_interval => 0.1)
891
+ @client.should_receive(:publish).once
892
+ flexmock(EM).should_receive(:add_timer).once
893
+ @sender.send(:amqp_send_retry, @packet, @token).should be_true
894
+ end
895
+
896
+ it "stops retrying if response was received" do
897
+ @sender = create_sender(:amqp, :retry_timeout => 0.3, :retry_interval => 0.1)
898
+ @client.should_receive(:publish).once
899
+ flexmock(EM).should_receive(:add_timer).and_yield.once
900
+ @sender.send(:amqp_send_retry, @packet, @token).should be_true
901
+ @sender.pending_requests[@token].should be_nil
902
+ end
903
+
904
+ it "keeps retrying if has not exceeded retry timeout" do
905
+ EM.run do
906
+ @sender = create_sender(:amqp, :retry_timeout => 0.3, :retry_interval => 0.1)
907
+ @client.should_receive(:publish).and_return(@broker_ids).twice
908
+ flexmock(@sender.connectivity_checker).should_receive(:check).once
909
+ @sender.send(:amqp_send, :send_request, @target, @packet, @received_at, @callback).should be_true
910
+
911
+ EM.add_timer(0.15) do
912
+ @sender.pending_requests.empty?.should be_false
913
+ result = RightScale::Result.new(@token, nil, RightScale::OperationResult.success, nil)
914
+ @sender.handle_response(result)
915
+ end
916
+
917
+ EM.add_timer(0.3) do
918
+ EM.stop
919
+ @response.results.success?.should be_true
920
+ @sender.pending_requests.empty?.should be_true
921
+ end
922
+ end
923
+ end
924
+
925
+ it "stops retrying and responds with non-delivery result if exceeds retry timeout" do
926
+ EM.run do
927
+ @sender = create_sender(:amqp, :retry_timeout => 0.1, :retry_interval => 0.1)
928
+ @client.should_receive(:publish).and_return(@broker_ids).once
929
+ @log.should_receive(:warning).once
930
+ flexmock(@sender.connectivity_checker).should_receive(:check).once
931
+ @sender.send(:amqp_send, :send_request, @target, @packet, @received_at, @callback).should be_true
932
+
933
+ EM.add_timer(0.3) do
934
+ EM.stop
935
+ @response.results.non_delivery?.should be_true
936
+ @response.results.content.should == RightScale::OperationResult::RETRY_TIMEOUT
937
+ @sender.pending_requests.empty?.should be_true
938
+ end
939
+ end
940
+ end
941
+
942
+ it "stops retrying if temporarily offline" do
943
+ @sender = create_sender(:amqp, :retry_timeout => 0.3, :retry_interval => 0.1)
944
+ @client.should_receive(:publish).and_return(@broker_ids).once.ordered
945
+ @client.should_receive(:publish).and_raise(RightAMQP::HABrokerClient::NoConnectedBrokers).once.ordered
946
+ flexmock(EM).should_receive(:add_timer).and_yield.once
947
+ @log.should_receive(:error).with(/Failed to publish request/, RightAMQP::HABrokerClient::NoConnectedBrokers).once
948
+ @log.should_receive(:error).with(/Failed retry.*temporarily offline/).once
949
+ @sender.send(:amqp_send, :send_request, @target, @packet, @received_at, @callback).should be_true
950
+ @sender.pending_requests[@token].should_not be_nil
951
+ @sender.pending_requests["retry token"].should_not be_nil
952
+ end
953
+
954
+ it "stops retrying if there is a send failure" do
955
+ @sender = create_sender(:amqp, :retry_timeout => 0.3, :retry_interval => 0.1)
956
+ @client.should_receive(:publish).and_return(@broker_ids).once.ordered
957
+ @client.should_receive(:publish).and_raise(StandardError, "failed").once.ordered
958
+ flexmock(EM).should_receive(:add_timer).and_yield.once
959
+ @log.should_receive(:error).with(/Failed to publish request/, StandardError, :trace).once
960
+ @log.should_receive(:error).with(/Failed retry.*send failure/).once
961
+ @sender.send(:amqp_send, :send_request, @target, @packet, @received_at, @callback).should be_true
962
+ @sender.pending_requests[@token].should_not be_nil
963
+ @sender.pending_requests["retry token"].should_not be_nil
964
+ end
965
+
966
+ it "stops retrying if there is an unexpected exception" do
967
+ # This will actually call amqp_send_retry 3 times recursively because add_timer always yields immediately
968
+ @sender = create_sender(:amqp, :retry_timeout => 0.3, :retry_interval => 0.1)
969
+ @client.should_receive(:publish).and_return(@broker_ids)
970
+ @log.should_receive(:error).with(/Failed retry.*without responding/, StandardError, :trace).twice
971
+ flexmock(EM).should_receive(:add_timer).and_yield
972
+ flexmock(@sender.connectivity_checker).should_receive(:check).and_raise(StandardError).once
973
+ @sender.send(:amqp_send, :send_request, @target, @packet, @received_at, @callback).should be_true
974
+ @sender.pending_requests[@token].should_not be_nil
975
+ @sender.pending_requests["retry token"].should_not be_nil
976
+ end
977
+ end
978
+ end
979
+ end
980
+
981
+ context :deliver_response do
982
+ before(:each) do
983
+ @received_at = Time.now
984
+ flexmock(Time).should_receive(:now).and_return(@received_at)
985
+ @response = nil
986
+ @callback = lambda { |response| @response = response }
987
+ end
988
+
989
+ it "calls the response handler" do
990
+ pending_request = RightScale::PendingRequest.new(:send_request, @received_at, @callback)
991
+ @sender.pending_requests[@token] = pending_request
992
+ response = RightScale::Result.new(@token, "to", RightScale::OperationResult.success, @target)
993
+ @sender.send(:deliver_response, response, pending_request).should be_true
994
+ @response.should == response
995
+ end
996
+
997
+ it "deletes associated pending request if is it a Request" do
998
+ pending_request = RightScale::PendingRequest.new(:send_request, @received_at, @callback)
999
+ @sender.pending_requests[@token] = pending_request
1000
+ response = RightScale::Result.new(@token, "to", RightScale::OperationResult.success, @target)
1001
+ @sender.send(:deliver_response, response, pending_request).should be_true
1002
+ @sender.pending_requests[@token].should be_nil
1003
+ end
1004
+
1005
+ it "does not delete pending request if it is a Push" do
1006
+ pending_request = RightScale::PendingRequest.new(:send_push, @received_at, @callback)
1007
+ @sender.pending_requests[@token] = pending_request
1008
+ response = RightScale::Result.new(@token, "to", RightScale::OperationResult.success, @target)
1009
+ @sender.send(:deliver_response, response, pending_request).should be_true
1010
+ @sender.pending_requests[@token].should_not be_nil
1011
+ end
1012
+
1013
+ it "deletes any associated retry requests" do
1014
+ @parent_token = RightSupport::Data::UUID.generate
1015
+ pending_request = RightScale::PendingRequest.new(:send_request, @received_at, @callback)
1016
+ @sender.pending_requests[@token] = pending_request
1017
+ @sender.pending_requests[@token].retry_parent_token = @parent_token
1018
+ @sender.pending_requests[@parent_token] = @sender.pending_requests[@token].dup
1019
+ response = RightScale::Result.new(@token, "to", RightScale::OperationResult.success, @target)
1020
+ @sender.send(:deliver_response, response, pending_request).should be_true
1021
+ @sender.pending_requests[@token].should be_nil
1022
+ @sender.pending_requests[@parent_token].should be_nil
1023
+ end
1024
+ end
1025
+
1026
+ context :queueing? do
1027
+ it "returns true if offline handling enabled and currently in queueing mode" do
1028
+ @sender = create_sender(:http, :offline_queueing => true)
1029
+ flexmock(@sender.offline_handler).should_receive(:queueing?).and_return(true)
1030
+ @sender.send(:queueing?).should be_true
1031
+ end
1032
+
1033
+ it "returns false if offline handling disabled" do
1034
+ flexmock(@sender.offline_handler).should_receive(:queueing?).and_return(true)
1035
+ @sender.send(:queueing?).should be_false
1036
+ end
1037
+
1038
+ it "returns false if offline handling enabled but not in queueing mode" do
1039
+ @sender = create_sender(:http, :offline_queueing => true)
1040
+ flexmock(@sender.offline_handler).should_receive(:queueing?).and_return(false)
1041
+ @sender.send(:queueing?).should be_false
1042
+ end
1043
+ end
1044
+ end
1045
+ end