right_agent 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. data/LICENSE +20 -0
  2. data/README.rdoc +78 -0
  3. data/Rakefile +86 -0
  4. data/lib/right_agent.rb +66 -0
  5. data/lib/right_agent/actor.rb +163 -0
  6. data/lib/right_agent/actor_registry.rb +76 -0
  7. data/lib/right_agent/actors/agent_manager.rb +189 -0
  8. data/lib/right_agent/agent.rb +735 -0
  9. data/lib/right_agent/agent_config.rb +403 -0
  10. data/lib/right_agent/agent_identity.rb +209 -0
  11. data/lib/right_agent/agent_tags_manager.rb +213 -0
  12. data/lib/right_agent/audit_formatter.rb +107 -0
  13. data/lib/right_agent/broker_client.rb +683 -0
  14. data/lib/right_agent/command.rb +30 -0
  15. data/lib/right_agent/command/agent_manager_commands.rb +134 -0
  16. data/lib/right_agent/command/command_client.rb +136 -0
  17. data/lib/right_agent/command/command_constants.rb +42 -0
  18. data/lib/right_agent/command/command_io.rb +128 -0
  19. data/lib/right_agent/command/command_parser.rb +87 -0
  20. data/lib/right_agent/command/command_runner.rb +105 -0
  21. data/lib/right_agent/command/command_serializer.rb +63 -0
  22. data/lib/right_agent/console.rb +65 -0
  23. data/lib/right_agent/core_payload_types.rb +42 -0
  24. data/lib/right_agent/core_payload_types/cookbook.rb +61 -0
  25. data/lib/right_agent/core_payload_types/cookbook_position.rb +46 -0
  26. data/lib/right_agent/core_payload_types/cookbook_repository.rb +116 -0
  27. data/lib/right_agent/core_payload_types/cookbook_sequence.rb +70 -0
  28. data/lib/right_agent/core_payload_types/dev_repositories.rb +90 -0
  29. data/lib/right_agent/core_payload_types/event_categories.rb +38 -0
  30. data/lib/right_agent/core_payload_types/executable_bundle.rb +138 -0
  31. data/lib/right_agent/core_payload_types/login_policy.rb +72 -0
  32. data/lib/right_agent/core_payload_types/login_user.rb +62 -0
  33. data/lib/right_agent/core_payload_types/planned_volume.rb +94 -0
  34. data/lib/right_agent/core_payload_types/recipe_instantiation.rb +60 -0
  35. data/lib/right_agent/core_payload_types/repositories_bundle.rb +50 -0
  36. data/lib/right_agent/core_payload_types/right_script_attachment.rb +95 -0
  37. data/lib/right_agent/core_payload_types/right_script_instantiation.rb +73 -0
  38. data/lib/right_agent/core_payload_types/secure_document.rb +66 -0
  39. data/lib/right_agent/core_payload_types/secure_document_location.rb +63 -0
  40. data/lib/right_agent/core_payload_types/software_repository_instantiation.rb +61 -0
  41. data/lib/right_agent/daemonize.rb +35 -0
  42. data/lib/right_agent/dispatcher.rb +348 -0
  43. data/lib/right_agent/enrollment_result.rb +217 -0
  44. data/lib/right_agent/exceptions.rb +30 -0
  45. data/lib/right_agent/ha_broker_client.rb +1278 -0
  46. data/lib/right_agent/idempotent_request.rb +140 -0
  47. data/lib/right_agent/log.rb +418 -0
  48. data/lib/right_agent/monkey_patches.rb +29 -0
  49. data/lib/right_agent/monkey_patches/amqp_patch.rb +274 -0
  50. data/lib/right_agent/monkey_patches/ruby_patch.rb +49 -0
  51. data/lib/right_agent/monkey_patches/ruby_patch/array_patch.rb +29 -0
  52. data/lib/right_agent/monkey_patches/ruby_patch/darwin_patch.rb +24 -0
  53. data/lib/right_agent/monkey_patches/ruby_patch/linux_patch.rb +24 -0
  54. data/lib/right_agent/monkey_patches/ruby_patch/linux_patch/file_patch.rb +30 -0
  55. data/lib/right_agent/monkey_patches/ruby_patch/object_patch.rb +49 -0
  56. data/lib/right_agent/monkey_patches/ruby_patch/singleton_patch.rb +46 -0
  57. data/lib/right_agent/monkey_patches/ruby_patch/string_patch.rb +107 -0
  58. data/lib/right_agent/monkey_patches/ruby_patch/windows_patch.rb +32 -0
  59. data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/file_patch.rb +90 -0
  60. data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/process_patch.rb +63 -0
  61. data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/stdio_patch.rb +27 -0
  62. data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/time_patch.rb +55 -0
  63. data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/win32ole_patch.rb +34 -0
  64. data/lib/right_agent/multiplexer.rb +91 -0
  65. data/lib/right_agent/operation_result.rb +270 -0
  66. data/lib/right_agent/packets.rb +637 -0
  67. data/lib/right_agent/payload_formatter.rb +104 -0
  68. data/lib/right_agent/pid_file.rb +159 -0
  69. data/lib/right_agent/platform.rb +319 -0
  70. data/lib/right_agent/platform/darwin.rb +227 -0
  71. data/lib/right_agent/platform/linux.rb +268 -0
  72. data/lib/right_agent/platform/windows.rb +1204 -0
  73. data/lib/right_agent/scripts/agent_controller.rb +522 -0
  74. data/lib/right_agent/scripts/agent_deployer.rb +379 -0
  75. data/lib/right_agent/scripts/common_parser.rb +153 -0
  76. data/lib/right_agent/scripts/log_level_manager.rb +193 -0
  77. data/lib/right_agent/scripts/stats_manager.rb +256 -0
  78. data/lib/right_agent/scripts/usage.rb +58 -0
  79. data/lib/right_agent/secure_identity.rb +92 -0
  80. data/lib/right_agent/security.rb +32 -0
  81. data/lib/right_agent/security/cached_certificate_store_proxy.rb +63 -0
  82. data/lib/right_agent/security/certificate.rb +102 -0
  83. data/lib/right_agent/security/certificate_cache.rb +89 -0
  84. data/lib/right_agent/security/distinguished_name.rb +56 -0
  85. data/lib/right_agent/security/encrypted_document.rb +84 -0
  86. data/lib/right_agent/security/rsa_key_pair.rb +76 -0
  87. data/lib/right_agent/security/signature.rb +86 -0
  88. data/lib/right_agent/security/static_certificate_store.rb +69 -0
  89. data/lib/right_agent/sender.rb +937 -0
  90. data/lib/right_agent/serialize.rb +29 -0
  91. data/lib/right_agent/serialize/message_pack.rb +102 -0
  92. data/lib/right_agent/serialize/secure_serializer.rb +131 -0
  93. data/lib/right_agent/serialize/secure_serializer_initializer.rb +47 -0
  94. data/lib/right_agent/serialize/serializable.rb +135 -0
  95. data/lib/right_agent/serialize/serializer.rb +149 -0
  96. data/lib/right_agent/stats_helper.rb +731 -0
  97. data/lib/right_agent/subprocess.rb +38 -0
  98. data/lib/right_agent/tracer.rb +124 -0
  99. data/right_agent.gemspec +60 -0
  100. data/spec/actor_registry_spec.rb +81 -0
  101. data/spec/actor_spec.rb +99 -0
  102. data/spec/agent_config_spec.rb +226 -0
  103. data/spec/agent_identity_spec.rb +75 -0
  104. data/spec/agent_spec.rb +571 -0
  105. data/spec/broker_client_spec.rb +961 -0
  106. data/spec/command/agent_manager_commands_spec.rb +51 -0
  107. data/spec/command/command_io_spec.rb +93 -0
  108. data/spec/command/command_parser_spec.rb +79 -0
  109. data/spec/command/command_runner_spec.rb +72 -0
  110. data/spec/command/command_serializer_spec.rb +51 -0
  111. data/spec/core_payload_types/dev_repositories_spec.rb +64 -0
  112. data/spec/core_payload_types/executable_bundle_spec.rb +59 -0
  113. data/spec/core_payload_types/login_user_spec.rb +98 -0
  114. data/spec/core_payload_types/right_script_attachment_spec.rb +65 -0
  115. data/spec/core_payload_types/spec_helper.rb +23 -0
  116. data/spec/dispatcher_spec.rb +372 -0
  117. data/spec/enrollment_result_spec.rb +53 -0
  118. data/spec/ha_broker_client_spec.rb +1673 -0
  119. data/spec/idempotent_request_spec.rb +136 -0
  120. data/spec/log_spec.rb +177 -0
  121. data/spec/monkey_patches/amqp_patch_spec.rb +100 -0
  122. data/spec/monkey_patches/eventmachine_spec.rb +62 -0
  123. data/spec/monkey_patches/string_patch_spec.rb +99 -0
  124. data/spec/multiplexer_spec.rb +48 -0
  125. data/spec/operation_result_spec.rb +171 -0
  126. data/spec/packets_spec.rb +418 -0
  127. data/spec/platform/platform_spec.rb +60 -0
  128. data/spec/results_mock.rb +45 -0
  129. data/spec/secure_identity_spec.rb +50 -0
  130. data/spec/security/cached_certificate_store_proxy_spec.rb +56 -0
  131. data/spec/security/certificate_cache_spec.rb +71 -0
  132. data/spec/security/certificate_spec.rb +49 -0
  133. data/spec/security/distinguished_name_spec.rb +46 -0
  134. data/spec/security/encrypted_document_spec.rb +55 -0
  135. data/spec/security/rsa_key_pair_spec.rb +55 -0
  136. data/spec/security/signature_spec.rb +66 -0
  137. data/spec/security/static_certificate_store_spec.rb +52 -0
  138. data/spec/sender_spec.rb +887 -0
  139. data/spec/serialize/message_pack_spec.rb +131 -0
  140. data/spec/serialize/secure_serializer_spec.rb +102 -0
  141. data/spec/serialize/serializable_spec.rb +90 -0
  142. data/spec/serialize/serializer_spec.rb +174 -0
  143. data/spec/spec.opts +2 -0
  144. data/spec/spec_helper.rb +77 -0
  145. data/spec/stats_helper_spec.rb +681 -0
  146. data/spec/tracer_spec.rb +114 -0
  147. metadata +320 -0
@@ -0,0 +1,98 @@
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
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'lib', 'right_agent', 'core_payload_types'))
25
+
26
+ # Copy of the old model for RightScale::LoginUser before the public_keys member was added
27
+ module LoginUserSpec
28
+ class LoginUserBeforePublicKeys
29
+ include RightScale::Serializable
30
+
31
+ attr_accessor :uuid, :username, :public_key, :common_name, :superuser, :expires_at
32
+
33
+ def initialize(*args)
34
+ @uuid = args[0]
35
+ @username = args[1]
36
+ @public_key = args[2]
37
+ @common_name = args[3] || ''
38
+ @superuser = args[4] || false
39
+ @expires_at = Time.at(args[5]) if args[5] && (args[5] != 0)
40
+ end
41
+
42
+ def serialized_members
43
+ [ @uuid, @username, @public_key, @common_name, @superuser, @expires_at.to_i ]
44
+ end
45
+ end
46
+ end
47
+
48
+ describe RightScale::LoginUser do
49
+
50
+ # ensures that the serialization downgrade case works.
51
+ def test_serialization_downgrade(user, public_key)
52
+ json = user.to_json
53
+ old_json = json.gsub("RightScale::LoginUser", "LoginUserSpec::LoginUserBeforePublicKeys")
54
+ old_user = JSON.parse(old_json)
55
+ old_user.class.should == LoginUserSpec::LoginUserBeforePublicKeys
56
+ old_user.public_key.should == public_key
57
+ end
58
+
59
+ it 'should serialize old version without public_keys member' do
60
+ num = rand(2**32).to_s(32)
61
+ pub = rand(2**32).to_s(32)
62
+ public_key = "ssh-rsa #{pub} #{num}@rightscale.com"
63
+ user = LoginUserSpec::LoginUserBeforePublicKeys.new("v0-#{num}", "rs-#{num}", public_key, "#{num}@rightscale.old", true, nil)
64
+ json = user.to_json
65
+ json = json.gsub("LoginUserSpec::LoginUserBeforePublicKeys", "RightScale::LoginUser")
66
+ user = JSON.parse(json)
67
+ user.public_key.should == public_key
68
+ user.public_keys.should == [public_key]
69
+ test_serialization_downgrade(user, public_key)
70
+ end
71
+
72
+ it 'should serialize current version with single public_key' do
73
+ num = rand(2**32).to_s(32)
74
+ pub = rand(2**32).to_s(32)
75
+ public_key = "ssh-rsa #{pub} #{num}@rightscale.com"
76
+ user = RightScale::LoginUser.new("v0-#{num}", "rs-#{num}", public_key, "#{num}@rightscale.old", true, nil, nil)
77
+ json = user.to_json
78
+ user = JSON.parse(json)
79
+ user.class.should == RightScale::LoginUser
80
+ user.public_key.should == public_key
81
+ user.public_keys.should == [public_key]
82
+ test_serialization_downgrade(user, public_key)
83
+ end
84
+
85
+ it 'should serialize current version with multiple public_keys' do
86
+ num = rand(2**32).to_s(32)
87
+ public_keys = []
88
+ 3.times do
89
+ pub = rand(2**32).to_s(32)
90
+ public_keys << "ssh-rsa #{pub} #{num}@rightscale.com"
91
+ end
92
+ new_user = RightScale::LoginUser.new("v0-#{num}", "rs-#{num}", nil, "#{num}@rightscale.old", true, nil, public_keys)
93
+ new_user.public_key.should == public_keys.first
94
+ new_user.public_keys.should == public_keys
95
+ test_serialization_downgrade(new_user, public_keys.first)
96
+ end
97
+
98
+ end
@@ -0,0 +1,65 @@
1
+ #-- -*- mode: ruby; encoding: utf-8 -*-
2
+ # Copyright: Copyright (c) 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 NONINFRINGEMENT.
18
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
25
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'lib', 'right_agent', 'core_payload_types'))
26
+
27
+ module RightScale
28
+ describe RightScriptAttachment do
29
+ context 'as a class' do
30
+ it 'should compute a simple hash correctly' do
31
+ RightScriptAttachment.hash_for('http://foo.bar.baz/', 'index.html', 'bsthl').should ==
32
+ Digest::SHA1.hexdigest("http://foo.bar.baz/\000bsthl")
33
+ end
34
+
35
+ it 'should compute a hash with a query term correctly' do
36
+ RightScriptAttachment.hash_for('http://foo.bar.baz/?q=bar;baz=foo', 'index.html', 'bsthl').should ==
37
+ Digest::SHA1.hexdigest("http://foo.bar.baz/\000bsthl")
38
+ end
39
+ end
40
+
41
+ context 'as an instance' do
42
+ it 'should compute a simple hash correctly' do
43
+ RightScriptAttachment.new('http://foo.bar.baz/', 'index.html', 'bsthl').to_hash.should ==
44
+ Digest::SHA1.hexdigest("http://foo.bar.baz/\000bsthl")
45
+ end
46
+
47
+ it 'should compute a hash with a query term correctly' do
48
+ RightScriptAttachment.new('http://foo.bar.baz/?q=bar;baz=foo', 'index.html', 'bsthl').to_hash.should ==
49
+ Digest::SHA1.hexdigest("http://foo.bar.baz/\000bsthl")
50
+ end
51
+
52
+ it 'should know how to fill out a session' do
53
+ attachment = RightScriptAttachment.new('http://foo.bar.baz/', 'index.html', 'bsthl')
54
+ session = flexmock('session')
55
+ session.should_receive(:[]=).with('scope', 'attachments').once
56
+ session.should_receive(:[]=).with('resource', attachment.to_hash).once
57
+ session.should_receive(:[]=).with('url', 'http://foo.bar.baz/').once
58
+ session.should_receive(:[]=).with('etag', 'bsthl').once
59
+ session.should_receive(:to_s).and_return("blah").once
60
+ attachment.fill_out(session)
61
+ attachment.token.should == "blah"
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,23 @@
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', 'spec_helper'))
@@ -0,0 +1,372 @@
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
+ class Foo
26
+ include RightScale::Actor
27
+ expose :bar, :index, :i_kill_you
28
+ on_exception :handle_exception
29
+
30
+ def index(payload)
31
+ bar(payload)
32
+ end
33
+
34
+ def bar(payload)
35
+ ['hello', payload]
36
+ end
37
+
38
+ def i_kill_you(payload)
39
+ raise RuntimeError.new('I kill you!')
40
+ end
41
+
42
+ def handle_exception(method, deliverable, error)
43
+ end
44
+ end
45
+
46
+ class Bar
47
+ include RightScale::Actor
48
+ expose :i_kill_you
49
+ on_exception do |method, deliverable, error|
50
+ @scope = self
51
+ @called_with = [method, deliverable, error]
52
+ end
53
+
54
+ def i_kill_you(payload)
55
+ raise RuntimeError.new('I kill you!')
56
+ end
57
+ end
58
+
59
+ # No specs, simply ensures multiple methods for assigning on_exception callback,
60
+ # on_exception raises exception when called with an invalid argument.
61
+ class Doomed
62
+ include RightScale::Actor
63
+ on_exception do
64
+ end
65
+ on_exception lambda {}
66
+ on_exception :doh
67
+ end
68
+
69
+ # Mock the EventMachine deferrer.
70
+ class EMMock
71
+ def self.defer(op = nil, callback = nil)
72
+ callback.call(op.call)
73
+ end
74
+ end
75
+
76
+ # Mock the EventMachine deferrer but do not do callback.
77
+ class EMMockNoCallback
78
+ def self.defer(op = nil, callback = nil)
79
+ op.call
80
+ end
81
+ end
82
+
83
+ describe "RightScale::Dispatcher" do
84
+
85
+ include FlexMock::ArgumentTypes
86
+
87
+ before(:each) do
88
+ flexmock(RightScale::Log).should_receive(:error).by_default.and_return { |m| raise RightScale::Log.format(*m) }
89
+ flexmock(RightScale::Log).should_receive(:info).by_default
90
+ @now = Time.at(1000000)
91
+ flexmock(Time).should_receive(:now).and_return(@now).by_default
92
+ @broker = flexmock("Broker", :subscribe => true, :publish => true).by_default
93
+ @actor = Foo.new
94
+ @registry = RightScale::ActorRegistry.new
95
+ @registry.register(@actor, nil)
96
+ @agent = flexmock("Agent", :identity => "agent", :broker => @broker, :registry => @registry, :options => {}).by_default
97
+ @dispatcher = RightScale::Dispatcher.new(@agent)
98
+ @dispatcher.em = EMMock
99
+ end
100
+
101
+ describe "Dispatched cache" do
102
+
103
+ before(:each) do
104
+ @dispatched = RightScale::Dispatcher::Dispatched.new
105
+ @token1 = "token1"
106
+ @token2 = "token2"
107
+ @token3 = "token3"
108
+ end
109
+
110
+ context "when storing" do
111
+
112
+ it "should store request token" do
113
+ @dispatched.store(@token1)
114
+ @dispatched.instance_variable_get(:@cache)[@token1].should == @now.to_i
115
+ @dispatched.instance_variable_get(:@lru).should == [@token1]
116
+ end
117
+
118
+ it "should update lru list when store to existing entry" do
119
+ @dispatched.store(@token1)
120
+ @dispatched.instance_variable_get(:@cache)[@token1].should == @now.to_i
121
+ @dispatched.instance_variable_get(:@lru).should == [@token1]
122
+ @dispatched.store(@token2)
123
+ @dispatched.instance_variable_get(:@cache)[@token2].should == @now.to_i
124
+ @dispatched.instance_variable_get(:@lru).should == [@token1, @token2]
125
+ flexmock(Time).should_receive(:now).and_return(@now += 10)
126
+ @dispatched.store(@token1)
127
+ @dispatched.instance_variable_get(:@cache)[@token1].should == @now.to_i
128
+ @dispatched.instance_variable_get(:@lru).should == [@token2, @token1]
129
+ end
130
+
131
+ it "should remove old cache entries when store new one" do
132
+ @dispatched.store(@token1)
133
+ @dispatched.store(@token2)
134
+ @dispatched.instance_variable_get(:@cache).keys.should == [@token1, @token2]
135
+ @dispatched.instance_variable_get(:@lru).should == [@token1, @token2]
136
+ flexmock(Time).should_receive(:now).and_return(@now += RightScale::Dispatcher::Dispatched::MAX_AGE + 1)
137
+ @dispatched.store(@token3)
138
+ @dispatched.instance_variable_get(:@cache).keys.should == [@token3]
139
+ @dispatched.instance_variable_get(:@lru).should == [@token3]
140
+ end
141
+
142
+ end
143
+
144
+ context "when fetching" do
145
+
146
+ it "should fetch request and make it the most recently used" do
147
+ @dispatched.store(@token1)
148
+ @dispatched.store(@token2)
149
+ @dispatched.instance_variable_get(:@lru).should == [@token1, @token2]
150
+ @dispatched.fetch(@token1).should be_true
151
+ @dispatched.instance_variable_get(:@lru).should == [@token2, @token1]
152
+ end
153
+
154
+ it "should return false if fetch non-existent request" do
155
+ @dispatched.fetch(@token1).should be_false
156
+ @dispatched.store(@token1)
157
+ @dispatched.fetch(@token1).should be_true
158
+ @dispatched.fetch(@token2).should be_false
159
+ end
160
+
161
+ end
162
+
163
+ context "when retrieving stats" do
164
+
165
+ it "should return nil if cache empty" do
166
+ @dispatched.stats.should be_nil
167
+ end
168
+
169
+ it "should return total, youngest age, and oldest age" do
170
+ @dispatched.store(@token1)
171
+ flexmock(Time).should_receive(:now).and_return(@now += 10)
172
+ @dispatched.store(@token2)
173
+ stats = @dispatched.stats
174
+ stats["total"].should == 2
175
+ stats["youngest age"].should == 0
176
+ stats["oldest age"].should == 10
177
+ @dispatched.fetch(@token1)
178
+ stats = @dispatched.stats
179
+ stats["oldest age"].should == 0
180
+ end
181
+
182
+ end
183
+
184
+ end # Dispatched
185
+
186
+ it "should dispatch a request" do
187
+ req = RightScale::Request.new('/foo/bar', 'you', :token => 'token')
188
+ res = @dispatcher.dispatch(req)
189
+ res.should(be_kind_of(RightScale::Result))
190
+ res.token.should == 'token'
191
+ res.results.should == ['hello', 'you']
192
+ end
193
+
194
+ it "should dispatch a request to the default action" do
195
+ req = RightScale::Request.new('/foo', 'you', :token => 'token')
196
+ res = @dispatcher.dispatch(req)
197
+ res.should(be_kind_of(RightScale::Result))
198
+ res.token.should == req.token
199
+ res.results.should == ['hello', 'you']
200
+ end
201
+
202
+ it "should handle custom prefixes" do
203
+ @registry.register(Foo.new, 'umbongo')
204
+ req = RightScale::Request.new('/umbongo/bar', 'you')
205
+ res = @dispatcher.dispatch(req)
206
+ res.should(be_kind_of(RightScale::Result))
207
+ res.token.should == req.token
208
+ res.results.should == ['hello', 'you']
209
+ end
210
+
211
+ it "should call the on_exception callback if something goes wrong" do
212
+ flexmock(RightScale::Log).should_receive(:error).once
213
+ req = RightScale::Request.new('/foo/i_kill_you', nil)
214
+ flexmock(@actor).should_receive(:handle_exception).with(:i_kill_you, req, Exception).once
215
+ @dispatcher.dispatch(req)
216
+ end
217
+
218
+ it "should call on_exception Procs defined in a subclass with the correct arguments" do
219
+ flexmock(RightScale::Log).should_receive(:error).once
220
+ actor = Bar.new
221
+ @registry.register(actor, nil)
222
+ req = RightScale::Request.new('/bar/i_kill_you', nil)
223
+ @dispatcher.dispatch(req)
224
+ called_with = actor.instance_variable_get("@called_with")
225
+ called_with[0].should == :i_kill_you
226
+ called_with[1].should == req
227
+ called_with[2].should be_kind_of(RuntimeError)
228
+ called_with[2].message.should == 'I kill you!'
229
+ end
230
+
231
+ it "should call on_exception Procs defined in a subclass in the scope of the actor" do
232
+ flexmock(RightScale::Log).should_receive(:error).once
233
+ actor = Bar.new
234
+ @registry.register(actor, nil)
235
+ req = RightScale::Request.new('/bar/i_kill_you', nil)
236
+ @dispatcher.dispatch(req)
237
+ actor.instance_variable_get("@scope").should == actor
238
+ end
239
+
240
+ it "should log error if something goes wrong" do
241
+ RightScale::Log.should_receive(:error).once
242
+ req = RightScale::Request.new('/foo/i_kill_you', nil)
243
+ @dispatcher.dispatch(req)
244
+ end
245
+
246
+ it "should reject requests whose time-to-live has expired" do
247
+ flexmock(Time).should_receive(:now).and_return(Time.at(1000000)).by_default
248
+ flexmock(RightScale::Log).should_receive(:info).once.with(on {|arg| arg =~ /REJECT EXPIRED.*TTL 2 sec ago/})
249
+ @broker.should_receive(:publish).never
250
+ @dispatcher = RightScale::Dispatcher.new(@agent)
251
+ @dispatcher.em = EMMock
252
+ req = RightScale::Push.new('/foo/bar', 'you', :expires_at => @now.to_i + 8)
253
+ flexmock(Time).should_receive(:now).and_return(@now += 10)
254
+ @dispatcher.dispatch(req).should be_nil
255
+ end
256
+
257
+ it "should send non-delivery result if Request is rejected because its time-to-live has expired" do
258
+ flexmock(Time).should_receive(:now).and_return(Time.at(1000000)).by_default
259
+ flexmock(RightScale::Log).should_receive(:info).once.with(on {|arg| arg =~ /REJECT EXPIRED/})
260
+ @broker.should_receive(:publish).with(Hash, on {|arg| arg.class == RightScale::Result &&
261
+ arg.results.non_delivery? &&
262
+ arg.results.content == RightScale::OperationResult::TTL_EXPIRATION},
263
+ hsh(:persistent => true, :mandatory => true)).once
264
+ @dispatcher = RightScale::Dispatcher.new(@agent)
265
+ @dispatcher.em = EMMock
266
+ req = RightScale::Request.new('/foo/bar', 'you', :expires_at => @now.to_i + 8)
267
+ flexmock(Time).should_receive(:now).and_return(@now += 10)
268
+ @dispatcher.dispatch(req).should be_nil
269
+ end
270
+
271
+ it "should send error result instead of non-delivery if agent does not know about non-delivery" do
272
+ flexmock(Time).should_receive(:now).and_return(Time.at(1000000)).by_default
273
+ flexmock(RightScale::Log).should_receive(:info).once.with(on {|arg| arg =~ /REJECT EXPIRED/})
274
+ @broker.should_receive(:publish).with(Hash, on {|arg| arg.class == RightScale::Result &&
275
+ arg.results.error? &&
276
+ arg.results.content =~ /Could not deliver/},
277
+ hsh(:persistent => true, :mandatory => true)).once
278
+ @dispatcher = RightScale::Dispatcher.new(@agent)
279
+ @dispatcher.em = EMMock
280
+ req = RightScale::Request.new('/foo/bar', 'you', {:expires_at => @now.to_i + 8}, [12, 13])
281
+ flexmock(Time).should_receive(:now).and_return(@now += 10)
282
+ @dispatcher.dispatch(req).should be_nil
283
+ end
284
+
285
+ it "should not reject requests whose time-to-live has not expired" do
286
+ flexmock(Time).should_receive(:now).and_return(Time.at(1000000)).by_default
287
+ @dispatcher = RightScale::Dispatcher.new(@agent)
288
+ @dispatcher.em = EMMock
289
+ req = RightScale::Request.new('/foo/bar', 'you', :expires_at => @now.to_i + 11)
290
+ flexmock(Time).should_receive(:now).and_return(@now += 10)
291
+ res = @dispatcher.dispatch(req)
292
+ res.should(be_kind_of(RightScale::Result))
293
+ res.token.should == req.token
294
+ res.results.should == ['hello', 'you']
295
+ end
296
+
297
+ it "should not check age of requests with time-to-live check disabled" do
298
+ @dispatcher = RightScale::Dispatcher.new(@agent)
299
+ @dispatcher.em = EMMock
300
+ req = RightScale::Request.new('/foo/bar', 'you', :expires_at => 0)
301
+ res = @dispatcher.dispatch(req)
302
+ res.should(be_kind_of(RightScale::Result))
303
+ res.token.should == req.token
304
+ res.results.should == ['hello', 'you']
305
+ end
306
+
307
+ it "should reject duplicate requests" do
308
+ flexmock(RightScale::Log).should_receive(:info).once.with(on {|arg| arg =~ /REJECT DUP/})
309
+ EM.run do
310
+ @agent.should_receive(:options).and_return(:dup_check => true)
311
+ @dispatcher = RightScale::Dispatcher.new(@agent)
312
+ req = RightScale::Request.new('/foo/bar', 'you', :token => "try")
313
+ @dispatcher.instance_variable_get(:@dispatched).store(req.token)
314
+ @dispatcher.dispatch(req).should be_nil
315
+ EM.stop
316
+ end
317
+ end
318
+
319
+ it "should reject duplicate retry requests" do
320
+ flexmock(RightScale::Log).should_receive(:info).once.with(on {|arg| arg =~ /REJECT RETRY DUP/})
321
+ EM.run do
322
+ @agent.should_receive(:options).and_return(:dup_check => true)
323
+ @dispatcher = RightScale::Dispatcher.new(@agent)
324
+ req = RightScale::Request.new('/foo/bar', 'you', :token => "try")
325
+ req.tries.concat(["try1", "try2"])
326
+ @dispatcher.instance_variable_get(:@dispatched).store("try2")
327
+ @dispatcher.dispatch(req).should be_nil
328
+ EM.stop
329
+ end
330
+ end
331
+
332
+ it "should not reject non-duplicate requests" do
333
+ EM.run do
334
+ @agent.should_receive(:options).and_return(:dup_check => true)
335
+ @dispatcher = RightScale::Dispatcher.new(@agent)
336
+ req = RightScale::Request.new('/foo/bar', 'you', :token => "try")
337
+ req.tries.concat(["try1", "try2"])
338
+ @dispatcher.instance_variable_get(:@dispatched).store("try3")
339
+ @dispatcher.dispatch(req).should_not be_nil
340
+ EM.stop
341
+ end
342
+ end
343
+
344
+ it "should not check for duplicates if dup_check disabled" do
345
+ EM.run do
346
+ @dispatcher = RightScale::Dispatcher.new(@agent)
347
+ req = RightScale::Request.new('/foo/bar', 'you', :token => "try")
348
+ req.tries.concat(["try1", "try2"])
349
+ @dispatcher.instance_variable_get(:@dispatched).should be_nil
350
+ @dispatcher.dispatch(req).should_not be_nil
351
+ EM.stop
352
+ end
353
+ end
354
+
355
+ it "should return dispatch age of youngest unfinished request" do
356
+ @dispatcher.em = EMMockNoCallback
357
+ @dispatcher.dispatch_age.should be_nil
358
+ @dispatcher.dispatch(RightScale::Push.new('/foo/bar', 'you'))
359
+ @dispatcher.dispatch_age.should == 0
360
+ @dispatcher.dispatch(RightScale::Request.new('/foo/bar', 'you'))
361
+ flexmock(Time).should_receive(:now).and_return(@now += 100)
362
+ @dispatcher.dispatch_age.should == 100
363
+ end
364
+
365
+ it "should return dispatch age of nil if all requests finished" do
366
+ @dispatcher.dispatch_age.should be_nil
367
+ @dispatcher.dispatch(RightScale::Request.new('/foo/bar', 'you'))
368
+ flexmock(Time).should_receive(:now).and_return(@now += 100)
369
+ @dispatcher.dispatch_age.should be_nil
370
+ end
371
+
372
+ end # RightScale::Dispatcher