right_agent 2.0.7-x86-mingw32

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 (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