scout_agent 3.0.6 → 3.0.7
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/CHANGELOG +18 -0
- data/Rakefile +1 -1
- data/lib/scout_agent/agent/communication_agent.rb +5 -1
- data/lib/scout_agent/agent/master_agent.rb +6 -6
- data/lib/scout_agent/agent.rb +26 -5
- data/lib/scout_agent/api.rb +52 -12
- data/lib/scout_agent/assignment/configuration.rb +11 -0
- data/lib/scout_agent/assignment/identify.rb +26 -0
- data/lib/scout_agent/assignment/queue.rb +35 -0
- data/lib/scout_agent/assignment/reset.rb +30 -0
- data/lib/scout_agent/assignment/snapshot.rb +50 -4
- data/lib/scout_agent/assignment/start.rb +65 -2
- data/lib/scout_agent/assignment/status.rb +22 -3
- data/lib/scout_agent/assignment/stop.rb +42 -5
- data/lib/scout_agent/assignment/test.rb +366 -0
- data/lib/scout_agent/assignment/update.rb +20 -0
- data/lib/scout_agent/assignment/upload_log.rb +31 -1
- data/lib/scout_agent/assignment.rb +92 -13
- data/lib/scout_agent/core_extensions.rb +24 -5
- data/lib/scout_agent/dispatcher.rb +45 -1
- data/lib/scout_agent/lifeline.rb +7 -2
- data/lib/scout_agent/mission.rb +31 -11
- data/lib/scout_agent/order/check_in_order.rb +27 -1
- data/lib/scout_agent/order/snapshot_order.rb +16 -0
- data/lib/scout_agent/order.rb +57 -11
- data/lib/scout_agent/plan.rb +1 -1
- data/lib/scout_agent.rb +1 -1
- metadata +13 -5
@@ -2,9 +2,19 @@
|
|
2
2
|
# encoding: UTF-8
|
3
3
|
|
4
4
|
module ScoutAgent
|
5
|
+
#
|
6
|
+
# This is the namespace for the methods that turn a command-line invocation
|
7
|
+
# into a command executed by the agent.
|
8
|
+
#
|
5
9
|
module Dispatcher
|
10
|
+
###############
|
6
11
|
module_function
|
12
|
+
###############
|
7
13
|
|
14
|
+
#
|
15
|
+
# This method handles the command invocation process. The passed +args+ are
|
16
|
+
# parsed for switches and command, then the selected code is loaded and run.
|
17
|
+
#
|
8
18
|
def dispatch(args = ARGV)
|
9
19
|
switches = parse_switches(args)
|
10
20
|
assignment = parse_assignment(args)
|
@@ -12,6 +22,11 @@ module ScoutAgent
|
|
12
22
|
execute_assignment(assignment, code, switches, args)
|
13
23
|
end
|
14
24
|
|
25
|
+
#
|
26
|
+
# Removes all supported command-line switches from +args+. Each switch sets
|
27
|
+
# the corresponding value in the Plan and a Hash of all switches are
|
28
|
+
# returned from this method.
|
29
|
+
#
|
15
30
|
def parse_switches(args)
|
16
31
|
switches = { }
|
17
32
|
|
@@ -61,7 +76,7 @@ module ScoutAgent
|
|
61
76
|
"Take regular system snapshots." ) do |boolean|
|
62
77
|
switches[:periodic_snapshots] = boolean
|
63
78
|
end
|
64
|
-
opts.on( "--trusted
|
79
|
+
opts.on( "--trusted USER1,USER2,...", Array,
|
65
80
|
"A list of trusted XMPP users." ) do |users|
|
66
81
|
switches[:xmpp_trusted] = users
|
67
82
|
end
|
@@ -113,6 +128,14 @@ module ScoutAgent
|
|
113
128
|
switches
|
114
129
|
end
|
115
130
|
|
131
|
+
#
|
132
|
+
# Locates the command in +args+. This process aborts with an error message
|
133
|
+
# if the given command is malformed. Otherwise the provided name is
|
134
|
+
# returned.
|
135
|
+
#
|
136
|
+
# If a command is not given, the agent will default <tt>"identify"</tt> if
|
137
|
+
# not configured, <tt>"start"</tt> if not running, or <tt>"status"</tt>.
|
138
|
+
#
|
116
139
|
def parse_assignment(args)
|
117
140
|
assignment = args.shift.to_s.downcase
|
118
141
|
if assignment.empty?
|
@@ -132,6 +155,10 @@ module ScoutAgent
|
|
132
155
|
assignment
|
133
156
|
end
|
134
157
|
|
158
|
+
#
|
159
|
+
# Loads the code matching the +assignment+ command or dies with an error
|
160
|
+
# message if the name cannot be matched. Returns the loaded code file.
|
161
|
+
#
|
135
162
|
def load_assignment(assignment)
|
136
163
|
dir = LIB_DIR + "assignment"
|
137
164
|
matches = dir.entries.map { |path| path.to_s }.
|
@@ -145,6 +172,11 @@ module ScoutAgent
|
|
145
172
|
end
|
146
173
|
end
|
147
174
|
|
175
|
+
#
|
176
|
+
# Requires +code+, loads the Class indicated by +assignment+, build an
|
177
|
+
# instance passing +switches+ and +other_args+, then executes the command.
|
178
|
+
# This method exits with an error if the code cannot be loaded.
|
179
|
+
#
|
148
180
|
def execute_assignment(assignment, code, switches, other_args)
|
149
181
|
require code
|
150
182
|
class_name = code.basename(".rb").to_s.CamelCase
|
@@ -156,6 +188,10 @@ module ScoutAgent
|
|
156
188
|
loaded.new(switches, other_args).prepare_and_execute
|
157
189
|
end
|
158
190
|
|
191
|
+
#
|
192
|
+
# Abort with an error message to the user that says we matched +assignment+
|
193
|
+
# to multiple +matches+ and we need clarification.
|
194
|
+
#
|
159
195
|
def abort_with_ambiguous_assignment(assignment, matches)
|
160
196
|
choices = matches.map { |m| "'#{File.basename(m, '.rb')}'" }
|
161
197
|
choices[-2..-1] = choices[-2..-1].join(", or ")
|
@@ -164,10 +200,18 @@ module ScoutAgent
|
|
164
200
|
END_AMBIGUOUS
|
165
201
|
end
|
166
202
|
|
203
|
+
#
|
204
|
+
# Abort with an error message to the user that says we were unable to match
|
205
|
+
# +assignment+ to a known command.
|
206
|
+
#
|
167
207
|
def abort_with_unknown_assignment(assignment)
|
168
208
|
abort "Unknown command '#{assignment}'."
|
169
209
|
end
|
170
210
|
|
211
|
+
#
|
212
|
+
# Abort with an error message to the user that warns of a broken command in
|
213
|
+
# our system.
|
214
|
+
#
|
171
215
|
def abort_with_missing_code(class_name)
|
172
216
|
abort "Failed to load '#{class_name}'."
|
173
217
|
end
|
data/lib/scout_agent/lifeline.rb
CHANGED
@@ -3,8 +3,8 @@
|
|
3
3
|
|
4
4
|
module ScoutAgent
|
5
5
|
class Lifeline
|
6
|
-
NO_CONTACT_TIMEOUT =
|
7
|
-
CHECK_IN_FREQUENCY = 0.99 # gives us
|
6
|
+
NO_CONTACT_TIMEOUT = 5
|
7
|
+
CHECK_IN_FREQUENCY = 0.99 # gives us five check-ins before a cutoff
|
8
8
|
TERM_TO_KILL_PAUSE = 1
|
9
9
|
RELAUNCH_FREQUENCIES = [0, 1, 1, 2, 3, 5, 8, 13]
|
10
10
|
|
@@ -145,6 +145,11 @@ module ScoutAgent
|
|
145
145
|
status(@agent, :restarting)
|
146
146
|
close_reader
|
147
147
|
Process.term_or_kill(@child_pid, TERM_TO_KILL_PAUSE)
|
148
|
+
# make sure we kill a mission process as well if running
|
149
|
+
if @agent == "master" and (mission_pid = IDCard.new(:mission).pid)
|
150
|
+
log.info("Stopping 'mission'.")
|
151
|
+
Process.term_or_kill(mission_pid, TERM_TO_KILL_PAUSE)
|
152
|
+
end
|
148
153
|
end
|
149
154
|
|
150
155
|
def status(process, restarting = false)
|
data/lib/scout_agent/mission.rb
CHANGED
@@ -32,17 +32,24 @@ module ScoutAgent
|
|
32
32
|
end
|
33
33
|
|
34
34
|
# Creates a new Scout Plugin to run.
|
35
|
-
def initialize(id, name, last_run, memory, options)
|
35
|
+
def initialize(id, name, last_run, memory, options, log = WireTap.new(nil))
|
36
36
|
@id = id
|
37
37
|
@name = name
|
38
38
|
@last_run = last_run
|
39
39
|
@memory = memory
|
40
40
|
@options = options
|
41
|
-
@
|
42
|
-
@
|
41
|
+
@log = log
|
42
|
+
@mission_log_db = !testing? && Database.load(:mission_log, @log)
|
43
|
+
@queue_db = !testing? && Database.load(:queue, @log)
|
43
44
|
end
|
44
45
|
|
45
46
|
attr_reader :last_run
|
47
|
+
attr_reader :log
|
48
|
+
alias_method :logger, :log
|
49
|
+
|
50
|
+
def testing?
|
51
|
+
@id == :testing
|
52
|
+
end
|
46
53
|
|
47
54
|
def option(name)
|
48
55
|
@options[name] ||
|
@@ -132,13 +139,23 @@ module ScoutAgent
|
|
132
139
|
end
|
133
140
|
|
134
141
|
def each_queued_message(&block)
|
135
|
-
|
136
|
-
|
137
|
-
block
|
138
|
-
|
139
|
-
|
142
|
+
if testing?
|
143
|
+
Array(option(:__queue__)).each do |message|
|
144
|
+
if block.arity == 1
|
145
|
+
block[message]
|
146
|
+
else
|
147
|
+
block[message, Time.now]
|
148
|
+
end
|
149
|
+
end
|
150
|
+
else
|
151
|
+
while message = @queue_db.peek(@id)
|
152
|
+
if block.arity == 1
|
153
|
+
block[message[:fields]]
|
154
|
+
else
|
155
|
+
block[message[:fields], message[:created_at]]
|
156
|
+
end
|
157
|
+
@queue_db.dequeue(message[:id]) or return # prevent infinite loop
|
140
158
|
end
|
141
|
-
@queue_db.dequeue(message[:id]) or return # prevent infinite loop
|
142
159
|
end
|
143
160
|
end
|
144
161
|
|
@@ -201,10 +218,13 @@ module ScoutAgent
|
|
201
218
|
def write_reports_and_update_memory
|
202
219
|
%w[report hint alert error].each do |type|
|
203
220
|
Array(send("#{type}s")).each do |fields|
|
204
|
-
@mission_log_db.write_report(@id, type, fields)
|
221
|
+
@mission_log_db.write_report(@id, type, fields) unless testing?
|
222
|
+
log.debug("#{type}(#{fields.inspect.sub(/\A\{(.*)\}\z/, '\1')})")
|
205
223
|
end
|
206
224
|
end
|
207
|
-
|
225
|
+
unless testing?
|
226
|
+
@mission_log_db.update_mission_memory(@id, data_for_server[:memory])
|
227
|
+
end
|
208
228
|
end
|
209
229
|
end
|
210
230
|
|
@@ -3,18 +3,44 @@
|
|
3
3
|
|
4
4
|
module ScoutAgent
|
5
5
|
class Order
|
6
|
+
#
|
7
|
+
# This type of Order can be used to request a check-in over the XMPP
|
8
|
+
# interface. Times can also be cleared for the desired missions to ensure
|
9
|
+
# they're included in the check-in.
|
10
|
+
#
|
6
11
|
class CheckInOrder < Order
|
12
|
+
#
|
13
|
+
# The pattern of supported commands:
|
14
|
+
#
|
15
|
+
# checkin
|
16
|
+
# checkin 1
|
17
|
+
# checkin 1 2 3
|
18
|
+
# check-in
|
19
|
+
# check-in 1
|
20
|
+
# check-in 1 2 3
|
21
|
+
# check_in
|
22
|
+
# check_in 1
|
23
|
+
# check_in 1 2 3
|
24
|
+
#
|
7
25
|
MATCH_RE = /\A\s*check[-_]?in(?:\s+(.+?))?\s*\z/
|
8
26
|
|
27
|
+
#
|
28
|
+
# Returns the mission log database or exit()'s with an error if it cannot
|
29
|
+
# be loaded.
|
30
|
+
#
|
9
31
|
def self.mission_log
|
10
32
|
return @mission_log if defined? @mission_log
|
11
33
|
unless db = Database.load(:mission_log, log)
|
12
34
|
log.fatal("Could not load mission log database.")
|
13
|
-
exit
|
35
|
+
exit(1)
|
14
36
|
end
|
15
37
|
@mission_log = db
|
16
38
|
end
|
17
39
|
|
40
|
+
#
|
41
|
+
# Requests a check-in by waking up the master agent. All provided mission
|
42
|
+
# ID's will have their run times reset before the check-in requested.
|
43
|
+
#
|
18
44
|
def execute
|
19
45
|
if id_str = match_details.captures.first
|
20
46
|
ids = id_str.scan(/\d+/).map { |n| n.to_i }
|
@@ -3,9 +3,25 @@
|
|
3
3
|
|
4
4
|
module ScoutAgent
|
5
5
|
class Order
|
6
|
+
#
|
7
|
+
# This type of Order can be used to request a snapshot over the XMPP
|
8
|
+
# interface. All snapshots requested this way are forced and, as such, not
|
9
|
+
# subject to command waits.
|
10
|
+
#
|
6
11
|
class SnapshotOrder < Order
|
12
|
+
#
|
13
|
+
# The pattern of supported commands:
|
14
|
+
#
|
15
|
+
# snapshot
|
16
|
+
# snap-shot
|
17
|
+
# snap_shot
|
18
|
+
#
|
7
19
|
MATCH_RE = /\A\s*snap[-_]?shot\s*\z/
|
8
20
|
|
21
|
+
#
|
22
|
+
# Requests a snapshot via the API and then notifies the master agent to
|
23
|
+
# check-in pushing the snapshot to the server.
|
24
|
+
#
|
9
25
|
def execute
|
10
26
|
log.info("Requesting that a snapshot be taken.")
|
11
27
|
API.take_snapshot(:force)
|
data/lib/scout_agent/order.rb
CHANGED
@@ -2,66 +2,112 @@
|
|
2
2
|
# encoding: UTF-8
|
3
3
|
|
4
4
|
module ScoutAgent
|
5
|
+
#
|
6
|
+
# This object represents an order that can be understood over XMPP.
|
7
|
+
#
|
8
|
+
# Each subclass can can set a +MATCH_RE+ constant with a regular expression
|
9
|
+
# for orders it can handle or override the match?() class method, analyze the
|
10
|
+
# orders with code, and return +true+ or +false+ if it can handle it.
|
11
|
+
#
|
12
|
+
# The first subclass that matches will have it's execute() method called to
|
13
|
+
# process the order.
|
14
|
+
#
|
5
15
|
class Order
|
16
|
+
# The directory Order subclasses are loaded from.
|
6
17
|
ORDERS_DIR = LIB_DIR + "order"
|
7
18
|
|
19
|
+
# Set the log() used by all Order objects.
|
8
20
|
def self.log=(log)
|
9
21
|
@log = log
|
10
22
|
end
|
11
23
|
|
24
|
+
# Returns whatever log has been set by or log=() or a bit bucket log.
|
12
25
|
def self.log
|
13
26
|
@log ||= WireTap.new(nil)
|
14
27
|
end
|
15
28
|
|
29
|
+
# Returns an Array of all loaded subclasses.
|
16
30
|
def self.subclasses
|
17
31
|
@subclasses ||= Array.new
|
18
32
|
end
|
19
33
|
|
34
|
+
#
|
35
|
+
# Hooks into Ruby's class load process so we can notice subclasses as they
|
36
|
+
# are loaded.
|
37
|
+
#
|
20
38
|
def self.inherited(order)
|
21
39
|
subclasses << order
|
22
40
|
end
|
23
41
|
|
42
|
+
# Load all subclasses found in +ORDERS_DIR+ and return them.
|
24
43
|
def self.load_all
|
25
44
|
ORDERS_DIR.each_entry do |order|
|
26
45
|
require ORDERS_DIR + order unless order.basename.to_s[0] == ?.
|
27
46
|
end
|
28
47
|
subclasses
|
29
48
|
end
|
30
|
-
|
49
|
+
|
50
|
+
#
|
51
|
+
# Finds the first subclass that can handle +message+, optionally with the
|
52
|
+
# +modified_body+. This method will return +nil+ if a match cannot be made.
|
53
|
+
#
|
31
54
|
def self.can_handle?(message, modified_body = nil)
|
32
|
-
subclasses.each
|
55
|
+
subclasses.each { |order|
|
33
56
|
if match_details = order.match?(message, modified_body)
|
34
57
|
return order.new(message, match_details)
|
35
58
|
end
|
36
|
-
|
59
|
+
}
|
37
60
|
nil
|
38
61
|
end
|
39
62
|
|
63
|
+
#
|
64
|
+
# Returns +true+ if this type of object matches +message+, optionally with
|
65
|
+
# the +modified_body+.
|
66
|
+
#
|
67
|
+
# The default implementation looks for a +MATCH_RE+ constant and checks it
|
68
|
+
# against +modified_body+ or <tt>message.body</tt>. Subclasses are free to
|
69
|
+
# override this for more complex behavior though.
|
70
|
+
#
|
40
71
|
def self.match?(message, modified_body = nil)
|
41
72
|
const_defined?(:MATCH_RE) and
|
42
73
|
const_get(:MATCH_RE).match(modified_body || message.body)
|
43
74
|
end
|
44
|
-
|
75
|
+
|
76
|
+
#
|
77
|
+
# Returns an IDCard for the master agent that Order objects can use to
|
78
|
+
# signal it.
|
79
|
+
#
|
45
80
|
def self.master_agent
|
46
81
|
@master_agent ||= IDCard.new(:master)
|
47
82
|
end
|
48
83
|
|
84
|
+
#
|
85
|
+
# Creates an instance of this Order type for execution. The Order is passed
|
86
|
+
# the matched +message+ and the +match_details+.
|
87
|
+
#
|
49
88
|
def initialize(message, match_details)
|
50
89
|
@message = message
|
51
90
|
@match_details = match_details
|
52
91
|
end
|
53
92
|
|
54
|
-
|
93
|
+
# The message matched from XMPP.
|
94
|
+
attr_reader :message
|
95
|
+
#
|
96
|
+
# Any details returned from the match. This will be an Array of Regexp
|
97
|
+
# captures by default.
|
98
|
+
#
|
99
|
+
attr_reader :match_details
|
55
100
|
|
101
|
+
# A shortcut for the class method of the same name.
|
56
102
|
def log
|
57
|
-
|
58
|
-
end
|
59
|
-
|
60
|
-
def execute
|
61
|
-
raise NotImplementedError,
|
62
|
-
"Subclasses must override ScoutAgent::Order#execute()."
|
103
|
+
self.class.log
|
63
104
|
end
|
64
105
|
|
106
|
+
#
|
107
|
+
# This helper can be used to send an +ALRM+ signal to the master agent which
|
108
|
+
# triggers it to wake up and go to work. This is handy for making it notice
|
109
|
+
# changes faster.
|
110
|
+
#
|
65
111
|
def notify_master
|
66
112
|
self.class.master_agent.signal("ALRM")
|
67
113
|
rescue Exception # unable to signal process
|
data/lib/scout_agent/plan.rb
CHANGED
@@ -17,7 +17,7 @@ module ScoutAgent
|
|
17
17
|
|
18
18
|
# The default configuration for this agent.
|
19
19
|
def defaults
|
20
|
-
[ [:server_url, "
|
20
|
+
[ [:server_url, "https://beta.scoutapp.com"],
|
21
21
|
[:proxy_url, nil],
|
22
22
|
[:run_as_daemon, true],
|
23
23
|
[:logging_level, "INFO"],
|
data/lib/scout_agent.rb
CHANGED
@@ -66,7 +66,7 @@ module ScoutAgent
|
|
66
66
|
end
|
67
67
|
|
68
68
|
# The version of this agent.
|
69
|
-
VERSION = "3.0.
|
69
|
+
VERSION = "3.0.7".freeze
|
70
70
|
# A Pathname reference to the agent code directory, used in dynamic loading.
|
71
71
|
LIB_DIR = Pathname.new(File.dirname(__FILE__)) + agent_name
|
72
72
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: scout_agent
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.0.
|
4
|
+
version: 3.0.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- James Edward Gray II
|
@@ -12,7 +12,7 @@ autorequire:
|
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
14
|
|
15
|
-
date: 2009-04-
|
15
|
+
date: 2009-04-27 00:00:00 -05:00
|
16
16
|
default_executable:
|
17
17
|
dependencies:
|
18
18
|
- !ruby/object:Gem::Dependency
|
@@ -75,7 +75,12 @@ dependencies:
|
|
75
75
|
- !ruby/object:Gem::Version
|
76
76
|
version: 0.1.0
|
77
77
|
version:
|
78
|
-
description:
|
78
|
+
description: |
|
79
|
+
Scout is a full server monitoring solution. You can install standard plugins
|
80
|
+
to get started with basic monitoring right away, or build your own plugins to
|
81
|
+
address your specific needs. Scout can be tied into any monitoring strategy,
|
82
|
+
providing you data collection, trend analysis, email notifications and more.
|
83
|
+
|
79
84
|
email: support@highgroove.com
|
80
85
|
executables:
|
81
86
|
- scout_agent
|
@@ -102,6 +107,7 @@ files:
|
|
102
107
|
- lib/scout_agent/assignment/start.rb
|
103
108
|
- lib/scout_agent/assignment/status.rb
|
104
109
|
- lib/scout_agent/assignment/stop.rb
|
110
|
+
- lib/scout_agent/assignment/test.rb
|
105
111
|
- lib/scout_agent/assignment/update.rb
|
106
112
|
- lib/scout_agent/assignment/upload_log.rb
|
107
113
|
- lib/scout_agent/assignment.rb
|
@@ -139,6 +145,8 @@ files:
|
|
139
145
|
- LICENSE
|
140
146
|
has_rdoc: true
|
141
147
|
homepage: http://scoutapp.com
|
148
|
+
licenses: []
|
149
|
+
|
142
150
|
post_install_message: |+
|
143
151
|
Installing Scout's agent...
|
144
152
|
|
@@ -174,9 +182,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
174
182
|
requirements: []
|
175
183
|
|
176
184
|
rubyforge_project: scout
|
177
|
-
rubygems_version: 1.3.
|
185
|
+
rubygems_version: 1.3.2
|
178
186
|
signing_key:
|
179
|
-
specification_version:
|
187
|
+
specification_version: 3
|
180
188
|
summary: Scout makes monitoring and reporting on your servers as flexible and simple as possible.
|
181
189
|
test_files:
|
182
190
|
- test/ts_all.rb
|