scout 5.3.5 → 5.4.4.alpha

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 (66) hide show
  1. data/.gitignore +6 -0
  2. data/CHANGELOG +0 -12
  3. data/Gemfile +4 -0
  4. data/README +8 -0
  5. data/Rakefile +6 -108
  6. data/bin/scout +1 -0
  7. data/lib/scout.rb +5 -4
  8. data/lib/scout/command.rb +11 -12
  9. data/lib/scout/command/install.rb +1 -1
  10. data/lib/scout/command/run.rb +13 -1
  11. data/lib/scout/command/sign.rb +2 -8
  12. data/lib/scout/command/stream.rb +50 -0
  13. data/lib/scout/command/test.rb +1 -1
  14. data/lib/scout/daemon_spawn.rb +215 -0
  15. data/lib/scout/plugin.rb +20 -1
  16. data/lib/scout/server.rb +16 -111
  17. data/lib/scout/server_base.rb +100 -0
  18. data/lib/scout/streamer.rb +162 -0
  19. data/lib/scout/streamer_control.rb +43 -0
  20. data/lib/scout/version.rb +3 -0
  21. data/scout.gemspec +27 -0
  22. data/test/plugins/disk_usage.rb +86 -0
  23. data/test/scout_test.rb +598 -0
  24. data/vendor/pusher-gem/Gemfile +2 -0
  25. data/vendor/pusher-gem/LICENSE +20 -0
  26. data/vendor/pusher-gem/README.md +80 -0
  27. data/vendor/pusher-gem/Rakefile +11 -0
  28. data/vendor/pusher-gem/examples/async_message.rb +28 -0
  29. data/vendor/pusher-gem/lib/pusher.rb +107 -0
  30. data/vendor/pusher-gem/lib/pusher/channel.rb +154 -0
  31. data/vendor/pusher-gem/lib/pusher/request.rb +107 -0
  32. data/vendor/pusher-gem/pusher.gemspec +28 -0
  33. data/vendor/pusher-gem/spec/channel_spec.rb +274 -0
  34. data/vendor/pusher-gem/spec/pusher_spec.rb +87 -0
  35. data/vendor/pusher-gem/spec/spec_helper.rb +13 -0
  36. data/vendor/ruby-hmac/History.txt +15 -0
  37. data/vendor/ruby-hmac/Manifest.txt +11 -0
  38. data/vendor/ruby-hmac/README.md +41 -0
  39. data/vendor/ruby-hmac/Rakefile +23 -0
  40. data/vendor/ruby-hmac/lib/hmac-md5.rb +11 -0
  41. data/vendor/ruby-hmac/lib/hmac-rmd160.rb +11 -0
  42. data/vendor/ruby-hmac/lib/hmac-sha1.rb +11 -0
  43. data/vendor/ruby-hmac/lib/hmac-sha2.rb +25 -0
  44. data/vendor/ruby-hmac/lib/hmac.rb +118 -0
  45. data/vendor/ruby-hmac/lib/ruby_hmac.rb +2 -0
  46. data/vendor/ruby-hmac/ruby-hmac.gemspec +33 -0
  47. data/vendor/ruby-hmac/test/test_hmac.rb +89 -0
  48. data/vendor/signature/.document +5 -0
  49. data/vendor/signature/.gitignore +21 -0
  50. data/vendor/signature/Gemfile +3 -0
  51. data/vendor/signature/Gemfile.lock +29 -0
  52. data/vendor/signature/LICENSE +20 -0
  53. data/vendor/signature/README.md +55 -0
  54. data/vendor/signature/Rakefile +2 -0
  55. data/vendor/signature/VERSION +1 -0
  56. data/vendor/signature/lib/signature.rb +142 -0
  57. data/vendor/signature/lib/signature/version.rb +3 -0
  58. data/vendor/signature/signature.gemspec +22 -0
  59. data/vendor/signature/spec/signature_spec.rb +176 -0
  60. data/vendor/signature/spec/spec_helper.rb +10 -0
  61. data/vendor/util/lib/core_extensions.rb +60 -0
  62. metadata +120 -84
  63. data/AUTHORS +0 -4
  64. data/COPYING +0 -340
  65. data/INSTALL +0 -18
  66. data/TODO +0 -6
@@ -0,0 +1,43 @@
1
+ module Scout
2
+ class StreamerControl < DaemonSpawn::Base
3
+
4
+ # args are: server, key, history, plugin_ids, streaming_key, log
5
+ def start(args)
6
+ puts "StreamerControl#start PID=#{pid}"
7
+ server,key,history,plugin_ids,streaming_key,log = args
8
+ $continue_streaming = true #
9
+
10
+ @scout = Scout::Streamer.new(server, key, history, plugin_ids, streaming_key, log)
11
+ puts "StreamerControl - done. Removing pid_file at #{pid_file} containing PID=#{pid}"
12
+ File.unlink(pid_file) if File.exists?(pid_file) # a better way of doing this?
13
+ end
14
+
15
+ def stop
16
+ $continue_streaming = false
17
+ end
18
+ end
19
+ end
20
+
21
+
22
+ # This is how to start using this file as a start/stop command processor, and passing arguments in via command line:
23
+ # Since there's no second argument to StreamerControl.spawn!, it uses command-line arguments.
24
+ #Scout::StreamerControl.spawn!({:log_file => File.expand_path('~/.scout/scout_streamer.log'),
25
+ # :pid_file => File.expand_path('~/.scout/scout_streamer.pid'),
26
+ # :sync_log => true,
27
+ # :working_dir => File.dirname(__FILE__)})
28
+
29
+
30
+ # This is how you start it from anywhere in code:
31
+ # Since there's a second argument to StreamerControl.spawn!, it uses those instead of command-line arguments.
32
+ #Scout::StreamerControl.start({:log_file => File.expand_path('~/.scout/scout_streamer.log'),
33
+ # :pid_file => File.expand_path('~/.scout/scout_streamer.pid'),
34
+ # :sync_log => true,
35
+ # :working_dir => File.dirname(__FILE__)},
36
+ # ["ServerInstance", "abcd-1234-123g2-12321", "~/.scout/history.yml",[1,2,3,4],nil])
37
+
38
+ # This is how you stop in anywhere in code:
39
+ # Since there's a second argument to StreamerControl.spawn!, it uses those instead of command-line arguments.
40
+ #Scout::StreamerControl.stop({:log_file => File.expand_path('~/.scout/scout_streamer.log'),
41
+ # :pid_file => File.expand_path('~/.scout/scout_streamer.pid'),
42
+ # :sync_log => true,
43
+ # :working_dir => File.dirname(__FILE__)},[])
@@ -0,0 +1,3 @@
1
+ module Scout
2
+ VERSION = "5.4.4.alpha"
3
+ end
data/scout.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "scout/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "scout"
7
+ s.version = Scout::VERSION
8
+ s.authors = ["Andre Lewis", "Derek Haynes", "James Edward Gray II"]
9
+ s.email = "support@scoutapp.com"
10
+ s.rubyforge_project = "scout"
11
+ s.homepage = "http://scoutapp.com"
12
+ s.summary = "Web-based monitoring, reporting, and alerting for your servers, clusters, and applications."
13
+ s.description = <<END_DESC
14
+ Scout makes monitoring and reporting on your web applications as flexible and simple as possible.
15
+ END_DESC
16
+
17
+ s.rubyforge_project = "scout"
18
+
19
+ s.files = `git ls-files`.split("\n")
20
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
21
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
22
+ s.require_paths = ["lib"]
23
+
24
+ # specify any dependencies here; for example:
25
+ # s.add_development_dependency "rspec"
26
+ s.add_runtime_dependency "elif"
27
+ end
@@ -0,0 +1,86 @@
1
+ class DiskUsage < Scout::Plugin
2
+
3
+ # the Disk Freespace RegEx
4
+ DF_RE = /\A\s*(\S.*?)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s*\z/
5
+
6
+ # Parses the file systems lines according to the Regular Expression
7
+ # DF_RE.
8
+ #
9
+ # normal line ex:
10
+ # /dev/disk0s2 233Gi 55Gi 177Gi 24% /
11
+
12
+ # multi-line ex:
13
+ # /dev/mapper/VolGroup00-LogVol00
14
+ # 29G 25G 2.5G 92% /
15
+ #
16
+ def parse_file_systems(io, &line_handler)
17
+ line_handler ||= lambda { |row| pp row }
18
+ headers = nil
19
+
20
+ row = ""
21
+ io.each do |line|
22
+ if headers.nil? and line =~ /\AFilesystem/
23
+ headers = line.split(" ", 6)
24
+ else
25
+ row << line
26
+ if row =~ DF_RE
27
+ fields = $~.captures
28
+ line_handler[headers ? Hash[*headers.zip(fields).flatten] : fields]
29
+ row = ""
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ # Ensures disk space metrics are in GB. Metrics that don't contain 'G,M,or K' are just
36
+ # turned into integers.
37
+ def clean_value(value)
38
+ if value =~ /G/i
39
+ value.to_i
40
+ elsif value =~ /M/i
41
+ (value.to_f/1024.to_f).round
42
+ elsif value =~ /K/i
43
+ (value.to_f/1024.to_f/1024.to_f).round
44
+ else
45
+ value.to_i
46
+ end
47
+ end
48
+
49
+ def build_report
50
+ ENV['lang'] = 'C' # forcing English for parsing
51
+ df_command = option("command") || "df -h"
52
+ df_output = `#{df_command}`
53
+
54
+ df_lines = []
55
+ parse_file_systems(df_output) { |row| df_lines << row }
56
+
57
+ # if the user specified a filesystem use that
58
+ df_line = nil
59
+ if option("filesystem")
60
+ df_lines.each do |line|
61
+ if line.has_value?(option("filesystem"))
62
+ df_line = line
63
+ end
64
+ end
65
+ end
66
+
67
+ # else just use the first line
68
+ df_line ||= df_lines.first
69
+
70
+ # remove 'filesystem' and 'mounted on' if present - these don't change.
71
+ df_line.reject! { |name,value| ['filesystem','mounted on'].include?(name.downcase.gsub(/\n/,'')) }
72
+
73
+ # capacity on osx = Use% on Linux ... convert anything that isn't size, used, or avail to capacity ... a big assumption?
74
+ assumed_capacity = df_line.find { |name,value| !['size','used','avail'].include?(name.downcase.gsub(/\n/,''))}
75
+ df_line.delete(assumed_capacity.first)
76
+ df_line['capacity'] = assumed_capacity.last
77
+
78
+ # will be passed at the end to report to Scout
79
+ report_data = Hash.new
80
+
81
+ df_line.each do |name, value|
82
+ report_data[name.downcase.strip.to_sym] = clean_value(value)
83
+ end
84
+ report(report_data)
85
+ end
86
+ end
@@ -0,0 +1,598 @@
1
+ # These are integration tests -- they require a local instance of the Scout server to run.
2
+ # If you only have the Scout Agent gem, these tests will not run successfully.
3
+ #
4
+ # Scout internal note: See documentation in scout_sinatra for running tests.
5
+ #
6
+ $VERBOSE=nil
7
+ $LOAD_PATH << File.expand_path( File.dirname(__FILE__) + '/../lib' )
8
+ require 'test/unit'
9
+ require 'lib/scout'
10
+ require "pty"
11
+ require "expect"
12
+
13
+ require 'rubygems'
14
+ require "active_record"
15
+ require "json" # the data format
16
+ require "erb" # only for loading rails DB config for now
17
+ require "logger"
18
+ require "newrelic_rpm"
19
+
20
+ SCOUT_PATH = '../scout'
21
+ SINATRA_PATH = '../scout_sinatra'
22
+ AGENT_DIR = File.expand_path( File.dirname(__FILE__) ) + '/working_dir/'
23
+ PATH_TO_DATA_FILE = File.join AGENT_DIR, 'history.yml'
24
+ AGENT_LOG = File.join AGENT_DIR, 'latest_run.log'
25
+ PLUGINS_PROPERTIES = File.join AGENT_DIR, 'plugins.properties'
26
+ PATH_TO_TEST_PLUGIN = File.expand_path( File.dirname(__FILE__) ) + '/plugins/temp_plugin.rb'
27
+
28
+ class ScoutTest < Test::Unit::TestCase
29
+ def setup
30
+ load_fixtures :clients, :plugins, :accounts, :subscriptions, :plugin_metas
31
+ clear_tables :plugin_activities, :ar_descriptors, :summaries
32
+ # delete the existing history file
33
+ File.unlink(PATH_TO_DATA_FILE) if File.exist?(PATH_TO_DATA_FILE)
34
+ File.unlink(AGENT_LOG) if File.exist?(AGENT_LOG)
35
+ File.unlink(PLUGINS_PROPERTIES) if File.exist?(PLUGINS_PROPERTIES)
36
+
37
+ Client.update_all "last_checkin='#{5.days.ago.strftime('%Y-%m-%d %H:%M')}'"
38
+ # ensures that fields are created
39
+ # Plugin.update_all "converted_at = '#{5.days.ago.strftime('%Y-%m-%d %H:%M')}'"
40
+ # clear out RRD files
41
+ Dir.glob(SCOUT_PATH+'/test/rrdbs/*.rrd').each { |f| File.unlink(f) }
42
+ @client=Client.find_by_key 'key', :include=>:plugins
43
+ @plugin=@client.plugins.first
44
+ # avoid client limit issues
45
+ assert @client.account.subscription.update_attribute(:clients,100)
46
+ end
47
+
48
+ def test_should_checkin_during_interactive_install
49
+ Client.update_all "last_checkin=null"
50
+ res=""
51
+ PTY.spawn("bin/scout -s http://localhost:4567 -d #{PATH_TO_DATA_FILE} install ") do | stdin, stdout, pid |
52
+ begin
53
+ stdin.expect("Enter the Server Key:", 3) do |response|
54
+ assert_not_nil response, "Agent didn't print prompt for server key"
55
+ stdout.puts @client.key # feed the agent the key
56
+ res=stdin.read.lstrip
57
+ end
58
+ rescue Errno::EIO
59
+ # don't care
60
+ end
61
+ end
62
+
63
+ assert res.match(/Attempting to contact the server.+Success!/m), "Output from interactive install session isn't right"
64
+
65
+ assert_in_delta Time.now.utc.to_i, @client.reload.last_ping.to_i, 100
66
+ assert_in_delta Time.now.utc.to_i, @client.reload.last_checkin.to_i, 100
67
+ end
68
+
69
+ def test_should_run_first_time
70
+ assert_nil @client.last_ping
71
+
72
+ scout(@client.key)
73
+ assert_in_delta Time.now.utc.to_i, @client.reload.last_ping.to_i, 100
74
+ assert_in_delta Time.now.utc.to_i, @client.reload.last_checkin.to_i, 100
75
+
76
+ assert_equal 'ping_key', history['directives']['ping_key']
77
+ end
78
+
79
+ def test_should_not_run_if_not_time_to_checkin
80
+ # do an initial checkin...should work
81
+ test_should_run_first_time
82
+
83
+ prev_checkin = @client.reload.last_checkin
84
+ sleep 2
85
+ scout(@client.key)
86
+ assert_equal prev_checkin, @client.reload.last_checkin
87
+ end
88
+
89
+ def test_should_run_when_forced
90
+ # do an initial checkin...should work
91
+ test_should_run_first_time
92
+
93
+ prev_checkin = @client.reload.last_checkin
94
+ sleep 2
95
+ scout(@client.key,'-F')
96
+ assert @client.reload.last_checkin > prev_checkin
97
+ end
98
+
99
+
100
+ # indirect way of assessing reuse: examining log
101
+ def test_reuse_existing_plan
102
+ test_should_run_first_time
103
+
104
+ res=scout(@client.key, '-v -ldebug')
105
+ assert_match "Plan not modified",res
106
+ end
107
+
108
+ def test_should_write_log_on_checkin
109
+ assert !File.exist?(AGENT_LOG)
110
+ test_should_run_first_time
111
+ assert File.exist?(AGENT_LOG)
112
+ end
113
+
114
+ def test_should_append_to_log_on_ping
115
+ test_should_run_first_time
116
+ assert File.exist?(AGENT_LOG)
117
+ log_file_size=File.read(AGENT_LOG).size
118
+ sleep 1
119
+ scout(@client.key)
120
+ assert File.read(AGENT_LOG).size > log_file_size, "log should be longer after ping"
121
+
122
+ end
123
+
124
+ def test_should_use_name_option
125
+ scout(@client.key,'--name="My New Server"')
126
+ assert_equal "My New Server", @client.reload.name
127
+ end
128
+
129
+ def test_should_not_change_name_when_not_provided
130
+ name=@client.name
131
+ scout(@client.key)
132
+ assert_equal name, @client.reload.name
133
+ end
134
+
135
+ def test_should_retrieve_new_plan
136
+ end
137
+
138
+ def test_should_checkin_even_if_history_file_not_writeable
139
+ end
140
+
141
+ def test_should_get_plan_with_blank_history_file
142
+ # Create a blank history file
143
+ File.open(PATH_TO_DATA_FILE, 'w+') {|f| f.write('') }
144
+
145
+ scout(@client.key)
146
+ assert_in_delta Time.now.utc.to_i, @client.reload.last_ping.to_i, 100
147
+ assert_in_delta Time.now.utc.to_i, @client.reload.last_checkin.to_i, 100
148
+ end
149
+
150
+ def test_should_generate_error_on_plugin_timeout
151
+ end
152
+
153
+
154
+ def test_should_generate_alert
155
+ prev_alerts = Alert.count
156
+
157
+ load_average = Plugin.find(1)
158
+
159
+ # In the real world, the md5 is taken care of automatically, and private key signing takes place manually.
160
+ # These extra steps are necessary because we have the Sinatra version of the models, not the Rails version.
161
+ new_code="class MyPlugin < Scout::Plugin; def build_report; alert('yo'); end; end"
162
+ load_average.meta.code = new_code
163
+ load_average.meta.save
164
+ load_average.signature=<<EOS
165
+ svVV4Qegk2KqqmiHW3ZzlAGFWZSVDPsWn6oCj6hLKWGEvupku7iltk8MLl9O
166
+ XIIzzCpkQ1M4izxiQKv+7V9+revh7WJQJDl4xdIL2laYBYRpjHr61YTjCnvw
167
+ /aJ1mx/dXHJ6JiYadrAHBIUty/387BAorytIINJppzVre5rWOKyI7ulpC423
168
+ 3v+qY6ZcpzUCvxDTI82x13xNcAfN6HkTE7RUtwhkaeKmJChEIwhiShdBirTP
169
+ dDLxK2GuTGFCn5PWJWvbryQJIjI6CbLGwxq8D8FaOiq6FojfjtsDS3oyR/Vl
170
+ 2EHBYcHwyZm6WcBypyXblUeqBfZLezfqF1QdYP76HA==
171
+ EOS
172
+ load_average.code_md5_signature=Digest::MD5.hexdigest(new_code)
173
+ load_average.save
174
+
175
+ scout(@client.key,'-F')
176
+
177
+ assert_in_delta Time.now.utc.to_i, @client.reload.last_checkin.to_i, 100
178
+ assert_equal prev_alerts + 1, Alert.count
179
+ end
180
+
181
+ def test_should_generate_report
182
+ prev_checkin = @client.reload.last_checkin
183
+ scout(@client.key,'-F')
184
+ assert_in_delta Time.now.utc.to_i, @client.reload.last_checkin.to_i, 100
185
+ load_average = Plugin.find(1)
186
+ assert_in_delta Time.now.utc.to_i, load_average.last_reported_at.to_i, 100
187
+ end
188
+
189
+ def test_should_set_config_path
190
+ assert @client.config_path.blank?
191
+ test_should_run_first_time
192
+ @client.reload
193
+ assert_equal AGENT_DIR, @client.config_path+"/"
194
+ end
195
+
196
+ def test_should_generate_summaries
197
+
198
+ end
199
+
200
+ def test_memory_should_be_stored
201
+
202
+ end
203
+
204
+ def test_client_version_is_set
205
+ assert_nil @client.last_ping
206
+ @client.update_attribute(:version,nil)
207
+ scout(@client.key)
208
+ assert_equal Gem::Version.new(Scout::VERSION), @client.reload.version
209
+ end
210
+
211
+ def test_client_hostname_is_set
212
+ assert_nil @client.hostname
213
+ scout(@client.key)
214
+ assert_equal `hostname`.chomp, @client.reload.hostname
215
+ end
216
+
217
+
218
+ def test_corrupt_history_file
219
+ File.open(PATH_TO_DATA_FILE,"w") do |f|
220
+ f.write <<-EOS
221
+ ---
222
+ memory: {}
223
+ 497081-Nginx monitoring: {}
224
+ EOS
225
+
226
+ end
227
+ scout(@client.key)
228
+
229
+ assert_in_delta Time.now.utc.to_i, @client.reload.last_checkin.to_i, 100, "should have checked in even though history file is corrupt"
230
+
231
+ corrupt_history_path=File.join AGENT_DIR, 'history.corrupt'
232
+ assert File.exist?(corrupt_history_path), "Should have backed up corrupted history file"
233
+ File.delete(corrupt_history_path) # just cleanup
234
+ end
235
+
236
+ def test_empty_history_file
237
+ # #there's no good way of testing this without complicated m
238
+ # File.open(PATH_TO_DATA_FILE,"w") {|f| f.write "" }
239
+ # #need to mock Scout::Server create_blank_history to make the file EMPTY for this test to be effective
240
+ # scout(@client.key,"-v -ldebug",true)
241
+ end
242
+
243
+ ####################
244
+ ### Test-Related ###
245
+ ####################
246
+ def test_runs_in_test_mode
247
+ code=<<-EOC
248
+ class StarterPlugin < Scout::Plugin
249
+ def build_report
250
+ report(:answer=>42)
251
+ end
252
+ end
253
+ EOC
254
+
255
+ run_scout_test(code) do |res|
256
+ assert ":fields=>{:answer=>42}", res
257
+ end
258
+ end
259
+
260
+
261
+ def test_embedded_options_in_test_mode
262
+ code=<<-EOC
263
+ class StarterPlugin < Scout::Plugin
264
+ OPTIONS=<<-EOS
265
+ lunch:
266
+ label: Lunch Time
267
+ default: 12
268
+ EOS
269
+ def build_report
270
+ report(:lunch_is_at => option(:lunch))
271
+ end
272
+ end
273
+ EOC
274
+
275
+ run_scout_test(code) do |res|
276
+ assert_match ":fields=>{:lunch_is_at=>12}", res
277
+ end
278
+ end
279
+
280
+ def test_command_line_options_in_test_mode
281
+ code=<<-EOC
282
+ class StarterPlugin < Scout::Plugin
283
+ OPTIONS=<<-EOS
284
+ lunch:
285
+ label: Lunch Time
286
+ default: 12
287
+ EOS
288
+ def build_report
289
+ report(:lunch_is_at => option(:lunch))
290
+ end
291
+ end
292
+ EOC
293
+
294
+ run_scout_test(code, 'lunch=13') do |res|
295
+ assert_match ':fields=>{:lunch_is_at=>"13"', res
296
+ end
297
+ end
298
+
299
+ # Needed to ensure that malformed embedded options don't bork the agent in test mode
300
+ def test_invalid_embedded_options_in_test_mode
301
+ code=<<-EOC
302
+ class StarterPlugin < Scout::Plugin
303
+ OPTIONS=<<-EOS
304
+ invalid yaml, oh noes!
305
+ EOS
306
+
307
+ def build_report
308
+ report(:answer=>42)
309
+ end
310
+ end
311
+ EOC
312
+ run_scout_test(code) do |res|
313
+ assert_match "Problem parsing option definition in the plugin code (ignoring and continuing)", res
314
+ assert_match ":fields=>{:answer=>42}", res
315
+ end
316
+ end
317
+
318
+ def test_plugin_properties
319
+
320
+ code=<<-EOC
321
+ class LookupTest < Scout::Plugin
322
+ OPTIONS=<<-EOS
323
+ foo:
324
+ default: 0
325
+ EOS
326
+ def build_report
327
+ report :foo_value=>option(:foo)
328
+ end
329
+ end
330
+ EOC
331
+
332
+ run_scout_test(code, 'foo=13') do |res|
333
+ assert_match ':fields=>{:foo_value=>"13"', res
334
+ end
335
+
336
+ properties=<<-EOS
337
+ # this is a properties file
338
+ myfoo=99
339
+ mybar=100
340
+ EOS
341
+
342
+ properties_path=File.join(AGENT_DIR,"plugins.properties")
343
+ File.open(properties_path,"w") {|f| f.write properties}
344
+
345
+ run_scout_test(code, 'foo=lookup:myfoo') do |res|
346
+ assert_match ':fields=>{:foo_value=>"99"', res
347
+ end
348
+
349
+ #cleanup
350
+ File.unlink(properties_path)
351
+ end
352
+
353
+ def test_plugin_override
354
+ override_path=File.join(AGENT_DIR,"#{@plugin.id}.rb")
355
+ code=<<-EOC
356
+ class OverrideTest < Scout::Plugin
357
+ def build_report; report(:foo=>99);end
358
+ end
359
+ EOC
360
+ File.open(override_path,"w"){|f|f.write(code)}
361
+
362
+ scout(@client.key)
363
+
364
+ report=YAML.load(@plugin.reload.last_report_raw)
365
+ assert report["foo"].is_a?(Array)
366
+ assert_equal 99, report["foo"].first
367
+ File.delete(override_path)
368
+ end
369
+
370
+ #def test_plugin_override_removed
371
+ # test_plugin_override
372
+ #
373
+ # # have to clear the RRD files so it doesn't complain about checking in to quickly
374
+ # Dir.glob(SCOUT_PATH+'/test/rrdbs/*.rrd').each { |f| File.unlink(f) }
375
+ # @plugin.rrdb_file.create_database(Time.now, [])
376
+ # scout(@client.key, "-F")
377
+ #
378
+ # report=YAML.load(@plugin.reload.last_report_raw)
379
+ # assert_nil report["foo"], "report shouldn't contain 'foo' field from the override"
380
+ # assert report["load"].is_a?(Array)
381
+ # assert_equal 2, report["load"].first
382
+ #end
383
+
384
+ def test_local_plugin
385
+ plugin_count=@client.plugins.count
386
+ local_path=File.join(AGENT_DIR,"my_local_plugin.rb")
387
+ code=<<-EOC
388
+ class LocalPluginTest < Scout::Plugin
389
+ def build_report; report(:answer=>42);end
390
+ end
391
+ EOC
392
+ File.open(local_path,"w"){|f|f.write(code)}
393
+
394
+ scout(@client.key)
395
+
396
+ assert_equal plugin_count+1, @client.reload.plugins.count, "there should be one additional plugin records -- created from the local plugin"
397
+
398
+ File.delete(local_path)
399
+ end
400
+
401
+
402
+ def test_streamer_plugin_compilation
403
+ # redefine the trigger! method, so the streamer doesn't loop indefinitely. We can't just mock it, because
404
+ # we need to set the $continue_streaming=false
405
+ Pusher::Channel.module_eval do
406
+ alias orig_trigger! trigger!
407
+ def trigger!(event_name, data, socket=nil)
408
+ $streamer_data = data
409
+ $continue_streaming=false
410
+ end
411
+ end
412
+ plugins=[]
413
+ acl_code="class AclPlugin < Scout::Plugin;def build_report; report(:value=>1);end;end"
414
+ acl_sig=<<EOS
415
+ QT/IYlR+/3h0YwBAHJeFz4HRFlisocVGorafNYJSYJC5RaUKqxu3dM+bOU4P
416
+ mQ5SmAt1mtXD5BJy2MeHam7Y8HAiWJbDBB318feZrC6xI2amu1b1/YMUyY8y
417
+ fMXS9z8J+ABsFIyV26av1KLxU1EHxi9iKxPwMg0HKJhTBStX4uIyncr/+ZSS
418
+ QKywEwPIPihFFyh9B2Z5WVSHtGcZG9CXDa20hrbQoNutOTniTkr00evBItYL
419
+ FN4L0F0ApIjTTkZW2vjzNR59j8HfZ7zrPfy33VhJkyAS0o9nQt5v0J5wKHj1
420
+ c3egj/Ffn/zSWZ1cTf3VSpfrGKUAlyB9KphZeYv2Og==
421
+ EOS
422
+ plugins << create_plugin(@client, "AclPlugin_1", acl_code, acl_sig)
423
+
424
+ code="class XYZPlugin < Scout::Plugin;def build_report; report(:value=>2);end;end"
425
+ sig=<<EOS
426
+ 6cNcDCM2GWcoT1Iqri+XFPgAiMxQaf0b8kOi4KKafNVD94cPkcy6OknNeQUM
427
+ v6GYcfGCAsiZvnjl/2wsqjvrAl/zyuSW/s5YLsjxca1LEvhkyxbpnDGuj32k
428
+ 3IuWKQ6JuEbmPXPP1aFsosOm7dbTCrjEn1fDQWAzmfCwznHV3MiqzvPD2D9g
429
+ 7gtxXcblNP6hm7A6AlBzP0hwYORR//gpLLGtmT5ewltHUj9aSUY0GQle3lvH
430
+ /uzBDoV1x6mEYR2jPO5QQxL3BvTBvpC06ec8M/ZWbO9IwA7/DOs+vYfngxlp
431
+ jbtpAK9QCaAalKy/Z29os/7aViHy9z9IVCpC/z3MDA==
432
+ EOS
433
+ plugins << create_plugin(@client, "XYZ Plugin", code, sig)
434
+
435
+ plugins << create_plugin(@client, "AclPlugin_2", acl_code, acl_sig)
436
+
437
+ scout(@client.key) # to write the initial history file. Sinatra MUST be running
438
+ $continue_streaming = true # so the streamer will run once
439
+ streamer=Scout::Streamer.new("http://none", "bogus_client_key", PATH_TO_DATA_FILE, [@client.plugins.first.id]+plugins.map(&:id), "bogus_streaming_key",nil) # for debugging, make last arg Logger.new(STDOUT)
440
+ res = $streamer_data # $streamer_data is set in the Channel.trigger! method we temporarily defined above
441
+
442
+ assert res.is_a?(Hash)
443
+ assert res[:plugins].is_a?(Array)
444
+ assert_equal 4, res[:plugins].size
445
+ assert_equal 2, res[:plugins][0][:fields][:load]
446
+ assert_equal 1, res[:plugins][1][:fields][:value]
447
+ assert_equal 2, res[:plugins][2][:fields][:value]
448
+ assert_equal 1, res[:plugins][3][:fields][:value]
449
+
450
+ Pusher::Channel.module_eval do
451
+ alias trigger! orig_trigger!
452
+ end
453
+ end
454
+
455
+ # test streamer starting and stopping
456
+ def test_streamer_process_management
457
+ streamer_pid_file = File.join(AGENT_DIR, "scout_streamer.pid")
458
+
459
+ test_should_run_first_time
460
+
461
+ assert !File.exist?(streamer_pid_file)
462
+
463
+ assert @client.update_attribute(:streamer_command, "start,abc,1,3")
464
+ scout(@client.key)
465
+ assert File.exist?(streamer_pid_file)
466
+ process_id = File.read(streamer_pid_file).to_i
467
+ assert process_running?(process_id)
468
+ assert_nil @client.reload.streamer_command
469
+
470
+ assert @client.update_attribute(:streamer_command, "stop")
471
+ scout(@client.key)
472
+ assert !File.exist?(streamer_pid_file)
473
+ sleep 2 # give process time to shut down
474
+ assert !process_running?(process_id)
475
+ assert_nil @client.reload.streamer_command
476
+ end
477
+
478
+
479
+ ######################
480
+ ### Helper Methods ###
481
+ ######################
482
+
483
+ # Runs the scout command with the given +key+ and +opts+ string (ex: '-F').
484
+ def scout(key, opts = nil, print_output=false)
485
+ opts = "" unless opts
486
+ cmd= "bin/scout #{key} -s http://localhost:4567 -d #{PATH_TO_DATA_FILE} #{opts} 2>&1"
487
+ puts "command: #{cmd}" if print_output
488
+ output=`#{cmd}`
489
+ puts output if print_output
490
+ output
491
+ end
492
+
493
+ # you can use this, but you have to create the plugin file and clean up afterwards.
494
+ # Or, you can use the blog version below
495
+ def scout_test(path_to_test_plugin, opts = String.new)
496
+ `bin/scout test #{path_to_test_plugin} -d #{PATH_TO_DATA_FILE} #{opts}`
497
+ end
498
+
499
+ # The preferred way to test the agent in test mode. This creates a plugin file with the code you provide,
500
+ # runs the agent in test mode, and cleans up the file.
501
+ def run_scout_test(code,opts=String.new)
502
+ File.open(PATH_TO_TEST_PLUGIN,"w") do |file|
503
+ file.write(code)
504
+ end
505
+
506
+ yield scout_test(PATH_TO_TEST_PLUGIN, opts)
507
+
508
+ ensure
509
+ File.unlink(PATH_TO_TEST_PLUGIN)
510
+ end
511
+
512
+
513
+ def history
514
+ YAML.load(File.read(PATH_TO_DATA_FILE))
515
+ end
516
+
517
+ def process_running?(pid)
518
+ begin
519
+ Process.getpgid( pid )
520
+ true
521
+ rescue Errno::ESRCH
522
+ false
523
+ end
524
+ end
525
+
526
+ # Establishes AR connection
527
+ def self.connect_ar
528
+ # ActiveRecord configuration
529
+ begin
530
+ $LOAD_PATH << File.join(SCOUT_PATH,'app/models')
531
+ # get an ActiveRecord connection
532
+ db_config_path=File.join(SCOUT_PATH,'config/database.yml')
533
+ db_config=YAML.load(ERB.new(File.read(db_config_path)).result)
534
+ db_hash=db_config['test']
535
+ # Store the full class name (including module namespace) in STI type column
536
+ # For triggers - before, just the class name and not the module name was stored,resulting in errors in the Rails
537
+ # app.
538
+ ActiveRecord::Base.store_full_sti_class = true
539
+ ActiveRecord::Base.establish_connection(db_hash)
540
+ # scout models and local models
541
+
542
+ require SINATRA_PATH + '/lib/ar_models.rb'
543
+
544
+ ActiveRecord::Base.default_timezone = :utc
545
+
546
+ puts "Established connection to Scout database :-)"
547
+ puts " #{Account.count} accounts there (sanity check)"
548
+ end
549
+ end
550
+
551
+ # ghetto fixture support
552
+ def load_fixtures(*table_names)
553
+ clear_tables(*table_names)
554
+ table_names.each do |table_name|
555
+ path = "#{SCOUT_PATH}/test/fixtures/#{table_name}.yml"
556
+ model_name = ActiveSupport::Inflector.classify table_name
557
+ model_class = ActiveRecord.const_get(model_name)
558
+
559
+ data = YAML.load_file(path)
560
+ data.each do |key, value_hash|
561
+ model_instance = model_class.new
562
+ model_instance.id = key.hash if !value_hash.has_key?(:id) # accounting for named foreign keys in fixtures, part 1
563
+ value_hash.each_pair do |k,v|
564
+ if model_instance.respond_to?(k+"_id") # accounting for named foreign keys in fixtures, part 2
565
+ model_instance.send "#{k}_id=",v.hash
566
+ else
567
+ model_instance.send "#{k}=",v
568
+ end
569
+ end
570
+ model_instance.save
571
+ end
572
+ end
573
+ end
574
+
575
+ def clear_tables(*tables)
576
+ tables.each do |table|
577
+ ActiveRecord::Base.connection.execute("truncate table #{table}")
578
+ end
579
+ end
580
+
581
+ # see scout's rake plugin:sign task to create the signatre
582
+ def create_plugin(client,name, code, signature)
583
+ p=client.plugins.create(:name=>name)
584
+ PluginMeta.create(:plugin=>p)
585
+ p.meta.code=code
586
+ p.code_md5_signature=Digest::MD5.hexdigest(code)
587
+ p.signature=signature
588
+ p.save
589
+ p.meta.save
590
+ puts "There was a problem creating '#{name}' plugin: #{p.errors.inspect}" if p.errors.any?
591
+ p
592
+ end
593
+
594
+ end
595
+
596
+ # Connect to AR before running
597
+ ScoutTest::connect_ar
598
+