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.
- data/.gitignore +6 -0
- data/CHANGELOG +0 -12
- data/Gemfile +4 -0
- data/README +8 -0
- data/Rakefile +6 -108
- data/bin/scout +1 -0
- data/lib/scout.rb +5 -4
- data/lib/scout/command.rb +11 -12
- data/lib/scout/command/install.rb +1 -1
- data/lib/scout/command/run.rb +13 -1
- data/lib/scout/command/sign.rb +2 -8
- data/lib/scout/command/stream.rb +50 -0
- data/lib/scout/command/test.rb +1 -1
- data/lib/scout/daemon_spawn.rb +215 -0
- data/lib/scout/plugin.rb +20 -1
- data/lib/scout/server.rb +16 -111
- data/lib/scout/server_base.rb +100 -0
- data/lib/scout/streamer.rb +162 -0
- data/lib/scout/streamer_control.rb +43 -0
- data/lib/scout/version.rb +3 -0
- data/scout.gemspec +27 -0
- data/test/plugins/disk_usage.rb +86 -0
- data/test/scout_test.rb +598 -0
- data/vendor/pusher-gem/Gemfile +2 -0
- data/vendor/pusher-gem/LICENSE +20 -0
- data/vendor/pusher-gem/README.md +80 -0
- data/vendor/pusher-gem/Rakefile +11 -0
- data/vendor/pusher-gem/examples/async_message.rb +28 -0
- data/vendor/pusher-gem/lib/pusher.rb +107 -0
- data/vendor/pusher-gem/lib/pusher/channel.rb +154 -0
- data/vendor/pusher-gem/lib/pusher/request.rb +107 -0
- data/vendor/pusher-gem/pusher.gemspec +28 -0
- data/vendor/pusher-gem/spec/channel_spec.rb +274 -0
- data/vendor/pusher-gem/spec/pusher_spec.rb +87 -0
- data/vendor/pusher-gem/spec/spec_helper.rb +13 -0
- data/vendor/ruby-hmac/History.txt +15 -0
- data/vendor/ruby-hmac/Manifest.txt +11 -0
- data/vendor/ruby-hmac/README.md +41 -0
- data/vendor/ruby-hmac/Rakefile +23 -0
- data/vendor/ruby-hmac/lib/hmac-md5.rb +11 -0
- data/vendor/ruby-hmac/lib/hmac-rmd160.rb +11 -0
- data/vendor/ruby-hmac/lib/hmac-sha1.rb +11 -0
- data/vendor/ruby-hmac/lib/hmac-sha2.rb +25 -0
- data/vendor/ruby-hmac/lib/hmac.rb +118 -0
- data/vendor/ruby-hmac/lib/ruby_hmac.rb +2 -0
- data/vendor/ruby-hmac/ruby-hmac.gemspec +33 -0
- data/vendor/ruby-hmac/test/test_hmac.rb +89 -0
- data/vendor/signature/.document +5 -0
- data/vendor/signature/.gitignore +21 -0
- data/vendor/signature/Gemfile +3 -0
- data/vendor/signature/Gemfile.lock +29 -0
- data/vendor/signature/LICENSE +20 -0
- data/vendor/signature/README.md +55 -0
- data/vendor/signature/Rakefile +2 -0
- data/vendor/signature/VERSION +1 -0
- data/vendor/signature/lib/signature.rb +142 -0
- data/vendor/signature/lib/signature/version.rb +3 -0
- data/vendor/signature/signature.gemspec +22 -0
- data/vendor/signature/spec/signature_spec.rb +176 -0
- data/vendor/signature/spec/spec_helper.rb +10 -0
- data/vendor/util/lib/core_extensions.rb +60 -0
- metadata +120 -84
- data/AUTHORS +0 -4
- data/COPYING +0 -340
- data/INSTALL +0 -18
- 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__)},[])
|
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
|
data/test/scout_test.rb
ADDED
@@ -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
|
+
|