scout 5.3.5 → 5.4.4.alpha

Sign up to get free protection for your applications and to get access to all the features.
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
+