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.
- 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
|
+
|