scout_agent 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. data/AUTHORS +4 -0
  2. data/CHANGELOG +3 -0
  3. data/COPYING +340 -0
  4. data/INSTALL +17 -0
  5. data/LICENSE +6 -0
  6. data/README +3 -0
  7. data/Rakefile +123 -0
  8. data/TODO +3 -0
  9. data/bin/scout_agent +11 -0
  10. data/lib/scout_agent.rb +73 -0
  11. data/lib/scout_agent/agent.rb +42 -0
  12. data/lib/scout_agent/agent/communication_agent.rb +85 -0
  13. data/lib/scout_agent/agent/master_agent.rb +301 -0
  14. data/lib/scout_agent/api.rb +241 -0
  15. data/lib/scout_agent/assignment.rb +105 -0
  16. data/lib/scout_agent/assignment/configuration.rb +30 -0
  17. data/lib/scout_agent/assignment/identify.rb +110 -0
  18. data/lib/scout_agent/assignment/queue.rb +95 -0
  19. data/lib/scout_agent/assignment/reset.rb +91 -0
  20. data/lib/scout_agent/assignment/snapshot.rb +92 -0
  21. data/lib/scout_agent/assignment/start.rb +149 -0
  22. data/lib/scout_agent/assignment/status.rb +44 -0
  23. data/lib/scout_agent/assignment/stop.rb +60 -0
  24. data/lib/scout_agent/assignment/upload_log.rb +61 -0
  25. data/lib/scout_agent/core_extensions.rb +260 -0
  26. data/lib/scout_agent/database.rb +386 -0
  27. data/lib/scout_agent/database/mission_log.rb +282 -0
  28. data/lib/scout_agent/database/queue.rb +126 -0
  29. data/lib/scout_agent/database/snapshots.rb +187 -0
  30. data/lib/scout_agent/database/statuses.rb +65 -0
  31. data/lib/scout_agent/dispatcher.rb +157 -0
  32. data/lib/scout_agent/id_card.rb +143 -0
  33. data/lib/scout_agent/lifeline.rb +243 -0
  34. data/lib/scout_agent/mission.rb +212 -0
  35. data/lib/scout_agent/order.rb +58 -0
  36. data/lib/scout_agent/order/check_in_order.rb +32 -0
  37. data/lib/scout_agent/order/snapshot_order.rb +33 -0
  38. data/lib/scout_agent/plan.rb +306 -0
  39. data/lib/scout_agent/server.rb +123 -0
  40. data/lib/scout_agent/tracked.rb +59 -0
  41. data/lib/scout_agent/wire_tap.rb +513 -0
  42. data/setup.rb +1360 -0
  43. data/test/tc_core_extensions.rb +89 -0
  44. data/test/tc_id_card.rb +115 -0
  45. data/test/tc_plan.rb +285 -0
  46. data/test/test_helper.rb +22 -0
  47. data/test/ts_all.rb +7 -0
  48. metadata +171 -0
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env ruby -wKU
2
+
3
+ require "test/unit"
4
+
5
+ require "scout_agent/core_extensions"
6
+
7
+ class TestCoreExtensions < Test::Unit::TestCase
8
+ def test_to_word_list
9
+ { %w[] => "",
10
+ %w[one] => "one",
11
+ %w[one two] => "one and two",
12
+ %w[one two three] => "one, two, and three",
13
+ %w[one two three four] => "one, two, three, and four" }.each do |arr, str|
14
+ assert_equal(str, arr.to_word_list)
15
+ end
16
+ end
17
+
18
+ def test_to_word_list_with_custom_conjunction
19
+ assert_equal("one, two, or three", %w[one two three].to_word_list("or"))
20
+ end
21
+
22
+ def test_string_camel_case
23
+ { "class_name" => "ClassName",
24
+ "symbols::are/removed" => "SymbolsAreRemoved",
25
+ "spaces are removed" => "SpacesAreRemoved",
26
+ "agent_99_smart" => "Agent99Smart",
27
+ "NoChange" => "NoChange" }.each do |snake_case, camel_case|
28
+ assert_equal(camel_case, snake_case.CamelCase)
29
+ end
30
+ end
31
+
32
+ def test_string_camel_case_alias
33
+ str = "test_string"
34
+ assert_equal(str.CamelCase, str.camel_case)
35
+ end
36
+
37
+ def test_string_snake_case
38
+ { "ClassName" => "class_name",
39
+ "Symbols::Are/Unified" => "symbols_are_unified",
40
+ "Spaces Are Converted" => "spaces_are_converted",
41
+ "Agent99Smart" => "agent_99_smart",
42
+ "no_change" => "no_change" }.each do |camel_case, snake_case|
43
+ assert_equal(snake_case, camel_case.snake_case)
44
+ end
45
+ end
46
+
47
+ def test_string_trim_with_default
48
+ heredoc = <<-END_DEFAULT.trim
49
+ I am indented on purpose.
50
+ Leading space should be removed.
51
+
52
+ but not these two spaces
53
+ END_DEFAULT
54
+ assert_match(/^I/, heredoc)
55
+ assert_match(/^Leading/, heredoc)
56
+ assert_match(/^ but/, heredoc)
57
+ end
58
+
59
+ def test_string_trim_with_width
60
+ heredoc = <<-END_WIDTH.trim(2)
61
+ I'm indented four,
62
+ but we will just trim two.
63
+ END_WIDTH
64
+ assert_match(/^ I'm/, heredoc)
65
+ assert_match(/^ but/, heredoc)
66
+ end
67
+
68
+ def test_to_question
69
+ assert_match(/\? \z/, "A simple question?\n\t ".to_question)
70
+ end
71
+
72
+ def test_word_wrap_with_default
73
+ assert_no_match(/^.{61}/, ("A short and simple string." * 1000).word_wrap)
74
+ end
75
+
76
+ def test_word_wrap_with_width
77
+ assert_no_match( /^.{11}/,
78
+ ("A short and simple string." * 1000).word_wrap(10) )
79
+ end
80
+
81
+ def test_word_wrap_skips_unbroken_content
82
+ assert_equal("0123456789", "0123456789".word_wrap(4))
83
+ end
84
+
85
+ def test_word_wrap_strips_and_simplifes_space
86
+ assert_equal( "one two\nthree",
87
+ " \t\none\n \ttwo three\n\n \t".word_wrap(10) )
88
+ end
89
+ end
@@ -0,0 +1,115 @@
1
+ #!/usr/bin/env ruby -wKU
2
+
3
+ require "test_helper"
4
+
5
+ require "scout_agent"
6
+
7
+ class TestIDCard < Test::Unit::TestCase
8
+ def setup
9
+ plan.prefix_path = test_prefix
10
+ plan.build_pid_dir(Process.egid)
11
+ end
12
+
13
+ def teardown
14
+ id_card(:test).revoke
15
+ test_prefix.rmtree if test_prefix.exist?
16
+ plan.reset_defaults
17
+ end
18
+
19
+ def test_authorize_creates_pid_file
20
+ @card = id_card(:test)
21
+ assert(!@card.pid_file.exist?, "PID file already existed")
22
+ assert(@card.authorize, "Failed to authorize our PID")
23
+ assert(@card.pid_file.exist?, "PID wasn't created")
24
+ assert_equal("#{Process.pid}\n", @card.pid_file.read)
25
+ end
26
+
27
+ def test_authorize_fails_for_an_existing_pid
28
+ test_authorize_creates_pid_file # make sure we already have a file
29
+ assert(!@card.authorize, "Authorized with an existing PID file")
30
+ end
31
+
32
+ def test_authorize_will_clear_a_stale_pid_file
33
+ create_and_kill_another_process
34
+ card = id_card(:test)
35
+ assert_not_equal(Process.pid, card.pid_file.read)
36
+ assert(card.authorize, "Failed to replace stale PID file")
37
+ assert_equal("#{Process.pid}\n", card.pid_file.read)
38
+ end
39
+
40
+ def test_successful_authorize_updates_me
41
+ ScoutAgent::IDCard.me = nil
42
+ test_authorize_creates_pid_file
43
+ assert_equal(@card, ScoutAgent::IDCard.me)
44
+ end
45
+
46
+ def test_revoke_clears_a_pid_file
47
+ test_authorize_creates_pid_file # create a file
48
+ assert(@card.revoke, "Failed to clear file")
49
+ end
50
+
51
+ def test_revoke_return_true_if_cleared_false_otherwise
52
+ test_authorize_creates_pid_file # create a file
53
+ assert(@card.revoke, "Failed to clear file")
54
+ assert(!@card.revoke, "Clear a PID file that was already cleared")
55
+ end
56
+
57
+ def test_pid_file_is_named_file
58
+ card = id_card(:my_name)
59
+ assert_match(/my_name\.pid\z/, card.pid_file.to_s)
60
+ end
61
+
62
+ def test_pid_is_a_shorcut_for_reading_pid_file
63
+ test_authorize_creates_pid_file # create a file
64
+ assert_equal(@card.pid_file.read.to_i, @card.pid)
65
+ end
66
+
67
+ def test_pid_returns_nil_for_a_missing_pid_file
68
+ assert_nil(id_card(:does_not_exist).pid)
69
+ end
70
+
71
+ def test_signal_delivers_messages
72
+ received_message = false
73
+ trap("USR1") { received_message = true }
74
+ test_authorize_creates_pid_file
75
+
76
+ # have another process send us a signal
77
+ other_pid = fork { @card.signal("USR1") }
78
+ Process.wait(other_pid)
79
+
80
+ assert(received_message, "Didn't receive signal")
81
+ end
82
+
83
+ def test_signal_returns_false_for_missing_pid_files
84
+ assert(!id_card(:does_not_exist).signal(0), "Signal was sent without a PID")
85
+ end
86
+
87
+ def test_signal_errors_bubble_up_to_caller
88
+ create_and_kill_another_process
89
+ card = id_card(:test)
90
+ assert_raise(Errno::ESRCH) { card.signal("KILL") }
91
+ end
92
+
93
+ def test_to_s_includes_process_name_and_pid
94
+ test_authorize_creates_pid_file # make an identity
95
+ assert_equal("test (#{Process.pid})", @card.to_s)
96
+ end
97
+
98
+ def test_to_s_uses_unauthorized_for_a_missing_pid
99
+ assert_equal("missing (unauthorized)", id_card(:missing).to_s)
100
+ end
101
+
102
+ private
103
+
104
+ def id_card(*args)
105
+ ScoutAgent::IDCard.new(*args)
106
+ end
107
+
108
+ def create_and_kill_another_process
109
+ other_pid = fork do
110
+ test_authorize_creates_pid_file # make sure we claim the PID file
111
+ exit! # and exist without clearing it
112
+ end
113
+ Process.wait(other_pid)
114
+ end
115
+ end
data/test/tc_plan.rb ADDED
@@ -0,0 +1,285 @@
1
+ #!/usr/bin/env ruby -wKU
2
+
3
+ require "test_helper"
4
+ require "tempfile"
5
+
6
+ require "scout_agent"
7
+
8
+ class TestPlan < Test::Unit::TestCase
9
+ def teardown
10
+ test_prefix.rmtree if test_prefix.exist?
11
+ end
12
+
13
+ ############
14
+ ### Type ###
15
+ ############
16
+
17
+ def test_plan_is_a_customized_ostruct
18
+ assert_instance_of(OpenStruct, plan)
19
+ end
20
+
21
+ ########################
22
+ ### Loading a Config ###
23
+ ########################
24
+
25
+ def test_updating_plan_from_a_config_file
26
+ c = load_config_file(<<-END_SETTINGS)
27
+ config.string_setting = "just a String"
28
+ config.integer_setting = 42
29
+ END_SETTINGS
30
+ assert_equal(plan, c)
31
+ assert_equal("just a String", c.string_setting)
32
+ assert_equal(42, c.integer_setting)
33
+ end
34
+
35
+ def test_exceptions_bubble_up_to_the_caller_from_config_file
36
+ assert_raise(RuntimeError) do
37
+ load_config_file('raise "Oops!"')
38
+ end
39
+ end
40
+
41
+ def test_updating_plan_from_switches
42
+ assert_nil(plan.switch_one)
43
+ assert_nil(plan.switch_two)
44
+ plan.update_from_switches(:switch_one => "One", :switch_two => "Two")
45
+ assert_equal("One", plan.switch_one)
46
+ assert_equal("Two", plan.switch_two)
47
+ end
48
+
49
+ ################
50
+ ### Defaults ###
51
+ ################
52
+
53
+ def test_defaults_are_available_in_an_enumerable_list
54
+ assert_kind_of(Enumerable, plan.defaults)
55
+ end
56
+
57
+ # def test_agent_name_is_set
58
+ # assert_match(/\A\w+\z/, plan.agent_name)
59
+ # assert_equal( plan.agent_name.split("_"),
60
+ # plan.proper_agent_name.downcase.split(" ") )
61
+ # end
62
+ #
63
+ # def test_agent_namespace_returns_module_based_on_agent_name
64
+ # assert_equal(ScoutAgent, plan.agent_namespace)
65
+ # end
66
+
67
+ def test_all_paths_return_pathname_objects
68
+ %w[ prefix_path
69
+ os_config_path
70
+ os_db_path
71
+ os_pid_path
72
+ os_log_path
73
+ config_file
74
+ db_dir
75
+ pid_dir
76
+ log_dir ].each do |path|
77
+ assert_instance_of(Pathname, plan(path))
78
+ end
79
+ end
80
+
81
+ def test_prefix_path_defaults_to_the_root_path
82
+ assert_equal(Pathname.new("/"), plan.prefix_path)
83
+ end
84
+
85
+ def test_default_os_paths_are_set
86
+ %w[ os_config_path
87
+ os_db_path
88
+ os_pid_path
89
+ os_log_path ].each do |path|
90
+ assert_path_match(%r{\A(?:/\w+)+/?\z}, path)
91
+ end
92
+ end
93
+
94
+ def test_default_paths_are_set
95
+ assert_path_match(%r{\A(?:/\w+)+\.\w+\z}, :config_file)
96
+ %w[db_dir pid_dir log_dir].each do |dir|
97
+ assert_path_match(%r{\A(?:/\w+)+/?\z}, dir)
98
+ end
99
+ end
100
+
101
+ def test_server_url_is_set
102
+ assert_match(%r{\Ahttps://}, plan.server_url)
103
+ end
104
+
105
+ def test_run_as_daemon_is_set
106
+ assert(plan.run_as_daemon, "Daemon mode should default to on")
107
+ end
108
+
109
+ def test_run_as_daemon_alias
110
+ assert_equal(plan.run_as_daemon, plan.run_as_daemon?)
111
+ end
112
+
113
+ def test_user_choices_is_set
114
+ assert_equal(%w[daemon nobody], plan.user_choices)
115
+ end
116
+
117
+ def test_group_choices_is_set
118
+ assert_equal(%w[daemon nogroup], plan.group_choices)
119
+ end
120
+
121
+ def test_reset_defaults
122
+ current = plan.defaults.map { |name, _| [name, plan(name)] }
123
+ plan.defaults.each { |name, _| plan("#{name}=", "Junk!") }
124
+ current.each { |name, value| assert_not_equal(value, plan(name)) }
125
+
126
+ plan.reset_defaults
127
+ current.each { |name, value| assert_equal(value, plan(name)) }
128
+ end
129
+
130
+ ####################
131
+ ### Nested Paths ###
132
+ ####################
133
+
134
+ def test_prefix_path_prepends_to_all_paths
135
+ %w[ os_config_path
136
+ os_db_path
137
+ os_pid_path
138
+ os_log_path
139
+ config_file
140
+ db_dir
141
+ pid_dir
142
+ log_dir ].each do |path|
143
+ configure(:prefix_path => test_prefix) do
144
+ assert_path_match(%r{\A#{test_prefix}/.+\z}, path)
145
+ end
146
+ end
147
+ end
148
+
149
+ def test_os_paths_prepends_to_full_paths
150
+ { :config_file => :os_config_path,
151
+ :db_dir => :os_db_path,
152
+ :pid_dir => :os_pid_path,
153
+ :log_dir => :os_log_path }.each do |path, parent|
154
+ assert_prepends_path(path, parent)
155
+ end
156
+ end
157
+
158
+ def test_prefix_path_nests_with_os_paths
159
+ dir = "test_dir"
160
+ { :config_file => :os_config_path,
161
+ :db_dir => :os_db_path,
162
+ :pid_dir => :os_pid_path,
163
+ :log_dir => :os_log_path }.each do |path, parent|
164
+ configure(:prefix_path => test_prefix, parent => dir) do
165
+ assert_path_match(%r{\A#{test_prefix}/#{dir}/.+\z}, path)
166
+ end
167
+ end
168
+ end
169
+
170
+ ################
171
+ ### Creation ###
172
+ ################
173
+
174
+ def test_write_default_config_file_creates_a_readable_ruby_configuration_file
175
+ configure(:prefix_path => test_prefix) do
176
+ assert(!File.exist?(plan.config_file), "Configuration already existed")
177
+ assert(plan.write_default_config_file, "Could not create config file")
178
+ assert(File.exist?(plan.config_file), "Configuration not created")
179
+ assert_equal("755", plan.config_file.stat.mode.to_s(8)[-3..-1])
180
+ end
181
+ end
182
+
183
+ def test_write_default_config_file_wont_replace_an_existing_file
184
+ configure(:prefix_path => test_prefix) do
185
+ assert(plan.write_default_config_file, "Could not create config file")
186
+ assert(!plan.write_default_config_file, "Replaced config file")
187
+ end
188
+ end
189
+
190
+ def test_builders_fails_if_they_dont_have_permission
191
+ configure(:prefix_path => test_prefix) do
192
+ { :write_default_config_file => :os_config_path,
193
+ :build_db_dir => :os_db_path,
194
+ :build_pid_dir => :os_pid_path,
195
+ :build_log_dir => :os_log_path }.each do |builder, dir|
196
+ plan(dir).mkpath
197
+ plan(dir).chmod(0444) # read only
198
+ args = builder == :write_default_config_file ? [ ] : [Process.egid]
199
+ assert(!plan(builder, *args), "Built without permission")
200
+ end
201
+ end
202
+ end
203
+
204
+ # def test_directory_builders_create_a_readable_and_writable_directory
205
+ # configure(:prefix_path => test_prefix) do
206
+ # %w[db_dir pid_dir log_dir].each do |dir|
207
+ # assert(!File.exist?(plan(dir)), "Directory already existed")
208
+ # assert(plan("build_#{dir}", Process.egid), "Could not create directory")
209
+ # assert(File.exist?(plan(dir)), "Directory not created")
210
+ # assert_equal("775", plan(dir).stat.mode.to_s(8)[-3..-1])
211
+ # end
212
+ # end
213
+ # end
214
+
215
+ ###################
216
+ ### Validations ###
217
+ ###################
218
+
219
+ # def test_plan_is_present
220
+ # configure(:prefix_path => test_prefix) do
221
+ # assert(!plan.present?, "Card was present when missing")
222
+ # # add some config
223
+ # %w[db_dir log_dir].each do |dir|
224
+ # assert(plan("build_#{dir}", Process.egid), "Could not create directory")
225
+ # end
226
+ # assert(!plan.present?, "Card was present when partially build")
227
+ # # complete config
228
+ # assert(plan.write_default_config_file, "Could not create config file")
229
+ # assert(plan.present?, "Complete card was not present")
230
+ # end
231
+ # end
232
+ #
233
+ # def test_plan_is_valid
234
+ # configure(:prefix_path => test_prefix) do
235
+ # assert(!plan.valid?, "Card was valid when missing")
236
+ # # add config
237
+ # %w[db_dir log_dir].each do |dir|
238
+ # assert(plan("build_#{dir}", Process.egid), "Could not create directory")
239
+ # end
240
+ # assert(plan.write_default_config_file, "Could not create config file")
241
+ # assert(plan.valid?, "Complete card was not valid")
242
+ # # break premissions
243
+ # plan.db_dir.chmod(0444) # read only
244
+ # assert(!plan.valid?, "Card was valid when read only")
245
+ # end
246
+ # end
247
+
248
+ #######
249
+ private
250
+ #######
251
+
252
+ # Creates a Tempfile, dumps configuration to it, and loads it.
253
+ def load_config_file(content = String.new)
254
+ cf = Tempfile.new("agent_config_test")
255
+ cf << content
256
+ cf.flush
257
+ plan.update_from_config_file(cf.path)
258
+ end
259
+
260
+ #
261
+ # Applies +settings+ to the configuaration, runs the passed block, then resets
262
+ # configuaration defaults.
263
+ #
264
+ def configure(settings)
265
+ settings.each { |name, value| plan("#{name}=", value) }
266
+ begin
267
+ yield
268
+ ensure
269
+ plan.reset_defaults
270
+ end
271
+ end
272
+
273
+ # Checks a Pathname +path+ against a +regexp+.
274
+ def assert_path_match(regexp, path)
275
+ assert_match(regexp, plan(path).to_s)
276
+ end
277
+
278
+ # Sets the +parent+ dir and ensures that +path+ is updated as a result.
279
+ def assert_prepends_path(path, parent)
280
+ dir = "test_dir"
281
+ configure(parent => dir) do
282
+ assert_path_match(%r{\A/#{dir}/.+\z}, path)
283
+ end
284
+ end
285
+ end