scout_agent 3.0.7 → 3.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +13 -0
- data/README +44 -2
- data/Rakefile +1 -1
- data/TODO +7 -1
- data/lib/scout_agent.rb +26 -1
- data/lib/scout_agent/agent/communication_agent.rb +75 -2
- data/lib/scout_agent/agent/master_agent.rb +132 -31
- data/lib/scout_agent/assignment/queue.rb +3 -0
- data/lib/scout_agent/assignment/snapshot.rb +4 -1
- data/lib/scout_agent/assignment/start.rb +3 -4
- data/lib/scout_agent/assignment/stop.rb +64 -30
- data/lib/scout_agent/id_card.rb +23 -12
- data/lib/scout_agent/lifeline.rb +129 -19
- data/lib/scout_agent/mission.rb +221 -68
- data/lib/scout_agent/order.rb +2 -2
- data/lib/scout_agent/plan.rb +38 -18
- metadata +2 -2
data/lib/scout_agent/mission.rb
CHANGED
@@ -2,20 +2,35 @@
|
|
2
2
|
# encoding: UTF-8
|
3
3
|
|
4
4
|
module ScoutAgent
|
5
|
+
#
|
6
|
+
# These objects are the data gathers periodically run by the agent. To gather
|
7
|
+
# a new type of data, you inherit from this Class and provide a build_report()
|
8
|
+
# method that calls some combination of report(), alert(), and error() to send
|
9
|
+
# data to the Scout server.
|
10
|
+
#
|
5
11
|
class Mission
|
6
|
-
|
7
|
-
|
12
|
+
#
|
13
|
+
# This method returns the last Mission subclass that was loaded. The
|
14
|
+
# Mission runner uses this after compiling Mission code to get the newly
|
15
|
+
# loaded object.
|
16
|
+
#
|
17
|
+
def self.prepared
|
18
|
+
@prepared ||= nil
|
8
19
|
end
|
9
|
-
|
10
|
-
|
11
|
-
|
20
|
+
|
21
|
+
#
|
22
|
+
# Hooks into Ruby's Class loading process so we can keep track of the last
|
23
|
+
# loaded subclass.
|
24
|
+
#
|
25
|
+
def self.inherited(new_mission)
|
26
|
+
@prepared = new_mission
|
12
27
|
end
|
13
28
|
|
14
29
|
#
|
15
|
-
# You can use this method to indicate one or more libraries your
|
30
|
+
# You can use this method to indicate one or more libraries your Mission
|
16
31
|
# requires:
|
17
32
|
#
|
18
|
-
# class MyNeedyPlugin <
|
33
|
+
# class MyNeedyPlugin < ScoutAgent::Mission
|
19
34
|
# needs "faster_csv", "elif"
|
20
35
|
# # ...
|
21
36
|
# end
|
@@ -25,13 +40,18 @@ module ScoutAgent
|
|
25
40
|
#
|
26
41
|
def self.needs(*libraries)
|
27
42
|
if libraries.empty?
|
28
|
-
@needs ||=
|
43
|
+
@needs ||= Array.new
|
29
44
|
else
|
30
45
|
needs.push(*libraries.flatten)
|
31
46
|
end
|
32
47
|
end
|
33
48
|
|
34
|
-
#
|
49
|
+
#
|
50
|
+
# Creates a new Scout Mission to run. It requires the +id+ and +name+ for
|
51
|
+
# the Mission as well as the +last_run+ Time, +memory+, and +options+ from
|
52
|
+
# the Mission database. You may optionally provide a +log+ for the Mission
|
53
|
+
# to write messages to.
|
54
|
+
#
|
35
55
|
def initialize(id, name, last_run, memory, options, log = WireTap.new(nil))
|
36
56
|
@id = id
|
37
57
|
@name = name
|
@@ -43,77 +63,145 @@ module ScoutAgent
|
|
43
63
|
@queue_db = !testing? && Database.load(:queue, @log)
|
44
64
|
end
|
45
65
|
|
66
|
+
#
|
67
|
+
# The numerical ID this Mission is known by to the Scout server. This is
|
68
|
+
# <tt>:testing</tt> for a Mission running in the agent's test mode.
|
69
|
+
#
|
70
|
+
attr_reader :id
|
71
|
+
# The human readable name of this Mission.
|
72
|
+
attr_reader :name
|
73
|
+
# The last run Time for this Mission. Can be +nil+ for new Missions.
|
46
74
|
attr_reader :last_run
|
75
|
+
# The log file this Mission is allowed to append messages to.
|
47
76
|
attr_reader :log
|
77
|
+
# A common alias used in Rails code.
|
48
78
|
alias_method :logger, :log
|
49
79
|
|
80
|
+
#
|
81
|
+
# Returns +true+ if this Mission is currently being run in the agent's test
|
82
|
+
# mode.
|
83
|
+
#
|
50
84
|
def testing?
|
51
85
|
@id == :testing
|
52
86
|
end
|
53
87
|
|
88
|
+
#
|
89
|
+
# Fetches an option passed to this Mission from the Web interface by +name+.
|
90
|
+
# If a direct lookup fails, it will be attempted again after converting a
|
91
|
+
# String +name+ to a Symbol or any other +name+ to a String. This makes the
|
92
|
+
# lookups feel familiar to HashWithIndifferentAccess fans.
|
93
|
+
#
|
54
94
|
def option(name)
|
55
95
|
@options[name] ||
|
56
96
|
@options[name.is_a?(String) ? name.to_sym : String(name)]
|
57
97
|
end
|
58
|
-
|
59
|
-
# Builds the data to send to the server.
|
98
|
+
|
60
99
|
#
|
61
|
-
#
|
100
|
+
# This method returns an Array of the reports that will be saved to the
|
101
|
+
# database at the end of this execution. It can be used to edit reports
|
102
|
+
# you have already entered.
|
62
103
|
#
|
63
|
-
|
104
|
+
def reports
|
105
|
+
data_for_server[:reports]
|
106
|
+
end
|
107
|
+
|
108
|
+
#
|
109
|
+
# This method adds a +new_report+ that will be saved to the database at the
|
110
|
+
# end of the current Mission run. The passed +new_report+ should be a Hash
|
111
|
+
# of the data values you wish to track.
|
64
112
|
#
|
65
|
-
|
66
|
-
|
67
|
-
|
113
|
+
def report(new_report)
|
114
|
+
reports << new_report
|
115
|
+
end
|
116
|
+
# Another way to add reports.
|
117
|
+
alias_method :add_report, :report
|
118
|
+
|
119
|
+
#
|
120
|
+
# This method returns an Array of the hints that will be saved to the
|
121
|
+
# database at the end of this execution. It can be used to edit hints you
|
122
|
+
# have already entered.
|
68
123
|
#
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
#
|
124
|
+
def hints
|
125
|
+
data_for_server[:hints]
|
126
|
+
end
|
127
|
+
|
128
|
+
#
|
129
|
+
# This method adds a +new_hint+ that will be saved to the database at the
|
130
|
+
# end of the current Mission run. The passed +new_hint+ should be a Hash of
|
131
|
+
# the data values you wish to track.
|
74
132
|
#
|
75
|
-
|
76
|
-
|
77
|
-
# error(:subject => "subject", :body => "body")
|
78
|
-
# add_error("subject", "body")
|
79
|
-
# add_error(:subject => "subject", :body => "body")
|
80
|
-
#
|
81
|
-
def data_for_server
|
82
|
-
@data_for_server ||= { :reports => [ ],
|
83
|
-
:hints => [ ],
|
84
|
-
:alerts => [ ],
|
85
|
-
:errors => [ ],
|
86
|
-
:memory => { } }
|
133
|
+
def hint(new_hint)
|
134
|
+
hints << new_hint
|
87
135
|
end
|
136
|
+
# Another way to add hints.
|
137
|
+
alias_method :add_hint, :hint
|
88
138
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
def #{kind}(new_entry)
|
97
|
-
#{kind}s << new_entry
|
98
|
-
end
|
99
|
-
else
|
100
|
-
def #{kind}(*fields)
|
101
|
-
#{kind}s << ( fields.first.is_a?(Hash) ?
|
102
|
-
fields.first :
|
103
|
-
{:subject => fields.first, :body => fields.last} )
|
104
|
-
end
|
105
|
-
end
|
106
|
-
alias_method :add_#{kind}, :#{kind}
|
107
|
-
END
|
139
|
+
#
|
140
|
+
# This method returns an Array of the alerts that will be saved to the
|
141
|
+
# database at the end of this execution. It can be used to edit alerts you
|
142
|
+
# have already entered.
|
143
|
+
#
|
144
|
+
def alerts
|
145
|
+
data_for_server[:alerts]
|
108
146
|
end
|
109
147
|
|
110
148
|
#
|
111
|
-
#
|
149
|
+
# :call-seq:
|
150
|
+
# alert(subject, body)
|
151
|
+
# alert(new_alert)
|
112
152
|
#
|
113
|
-
#
|
114
|
-
#
|
153
|
+
# This method adds an alert that will be saved to the database at the end of
|
154
|
+
# the current Mission run. You can pass the +subject+ and +body+ of the
|
155
|
+
# generated alert separately, or a Hash containing <tt>:subject</tt> and
|
156
|
+
# <tt>:body</tt> values for the +new_alert+.
|
157
|
+
#
|
158
|
+
def alert(*new_alert)
|
159
|
+
alerts << build_alert_or_error(new_alert)
|
160
|
+
end
|
161
|
+
# Another way to add alerts.
|
162
|
+
alias_method :add_alert, :alert
|
163
|
+
|
164
|
+
#
|
165
|
+
# This method returns an Array of the errors that will be saved to the
|
166
|
+
# database at the end of this execution. It can be used to edit errors you
|
167
|
+
# have already entered.
|
168
|
+
#
|
169
|
+
def errors
|
170
|
+
data_for_server[:errors]
|
171
|
+
end
|
172
|
+
|
173
|
+
#
|
174
|
+
# :call-seq:
|
175
|
+
# error(subject, body)
|
176
|
+
# error(new_error)
|
177
|
+
#
|
178
|
+
# This method adds an error that will be saved to the database at the end of
|
179
|
+
# the current Mission run. You can pass the +subject+ and +body+ of the
|
180
|
+
# generated error separately, or a Hash containing <tt>:subject</tt> and
|
181
|
+
# <tt>:body</tt> values for the +new_error+.
|
182
|
+
#
|
183
|
+
def error(*new_error)
|
184
|
+
errors << build_alert_or_error(new_error)
|
185
|
+
end
|
186
|
+
# Another way to add errors.
|
187
|
+
alias_method :add_error, :error
|
188
|
+
|
189
|
+
#
|
190
|
+
# :call-seq:
|
191
|
+
# memory(name)
|
192
|
+
# memory.delete(name)
|
115
193
|
# memory.clear
|
116
194
|
#
|
195
|
+
# The primary usage of this method is to fetch values saved into the Mission
|
196
|
+
# memory during a previous execution by +name+. The +name+ lookup rules are
|
197
|
+
# The same as those decribed in the option() method, so you can use String
|
198
|
+
# and Symbol keys interchangeably.
|
199
|
+
#
|
200
|
+
# When called without a +name+, this method returns a Hash of <b>the
|
201
|
+
# current</b> Mission memory (not the remembered values from the previous
|
202
|
+
# execution). This is mainly helpful if you want to edit the values you
|
203
|
+
# have added to be saved during this run.
|
204
|
+
#
|
117
205
|
def memory(name = nil)
|
118
206
|
if name.nil?
|
119
207
|
data_for_server[:memory]
|
@@ -124,20 +212,46 @@ module ScoutAgent
|
|
124
212
|
end
|
125
213
|
|
126
214
|
#
|
127
|
-
#
|
215
|
+
# :call-seq:
|
216
|
+
# remember(name, value)
|
217
|
+
# remember(name1, value1, name2, value2, ...)
|
218
|
+
# remember(name => value)
|
219
|
+
# remember(name1 => value1, name2 => value2, ...)
|
220
|
+
# remember(name1, value1, ..., name2 => value2, ...)
|
128
221
|
#
|
129
|
-
#
|
130
|
-
#
|
131
|
-
#
|
132
|
-
#
|
133
|
-
#
|
222
|
+
# Adds all +name+ and +value+ pairs into the Mission memory that will be
|
223
|
+
# saved from the current execution.
|
224
|
+
#
|
225
|
+
# It's important to note that Mission memory is not automatically copied
|
226
|
+
# from the previous executions, so you must call this method during each
|
227
|
+
# execution if you need to keep saving a value.
|
134
228
|
#
|
135
229
|
def remember(*args)
|
230
|
+
# divide Hash and non-Hash arguments
|
136
231
|
hashes, other = args.partition { |value| value.is_a? Hash }
|
137
|
-
|
138
|
-
|
232
|
+
# merge all Hash arguments into the Mission memory
|
233
|
+
hashes.each do |hash|
|
234
|
+
memory.merge!(hash)
|
235
|
+
end
|
236
|
+
# merge all non-Hash arguments into the Mission memory
|
237
|
+
(0...other.size).step(2) do |i|
|
238
|
+
memory.merge!(other[i] => other[i + 1])
|
239
|
+
end
|
139
240
|
end
|
140
241
|
|
242
|
+
#
|
243
|
+
# This method iterates over messages that have been queued from an external
|
244
|
+
# source for your Mission. Each message (the restored Ruby representation
|
245
|
+
# of the JSON data passed to the queue command) will be passed into your
|
246
|
+
# +block+ one by one. If your block accepts more than one argument, you
|
247
|
+
# will also be passed queue Times with the messages. The act of reading
|
248
|
+
# these messages removes them from the database so the current Mission
|
249
|
+
# should be used to pass them to the server as meaningful data or save them
|
250
|
+
# to Mission memory for later use.
|
251
|
+
#
|
252
|
+
# When a Mission is running in test mode, queued messages are read from
|
253
|
+
# <tt>options(:\_\_queue\_\_)</tt> and queue Times are simply <tt>Time.now</tt>.
|
254
|
+
#
|
141
255
|
def each_queued_message(&block)
|
142
256
|
if testing?
|
143
257
|
Array(option(:__queue__)).each do |message|
|
@@ -160,6 +274,10 @@ module ScoutAgent
|
|
160
274
|
end
|
161
275
|
|
162
276
|
#
|
277
|
+
# This method is the wrapper over Scout's older interface for Mission (then
|
278
|
+
# called a Plugin) execution. It hands off to the newer build_report(),
|
279
|
+
# then records the results of that run.
|
280
|
+
#
|
163
281
|
# Old plugins will work because they override this method. New plugins can
|
164
282
|
# now leave this method in place, add a build_report() method instead, and
|
165
283
|
# use the new helper methods to build up content inside which will
|
@@ -192,13 +310,15 @@ module ScoutAgent
|
|
192
310
|
write_reports_and_update_memory
|
193
311
|
end
|
194
312
|
|
313
|
+
#######
|
195
314
|
private
|
315
|
+
#######
|
196
316
|
|
197
317
|
#
|
198
|
-
# Returns true
|
199
|
-
# first attempt. If that fails, RubyGems is loaded and another attempt
|
200
|
-
# made. If the library cannot be loaded either way, an error() is
|
201
|
-
# and build_report() will not be called.
|
318
|
+
# Returns +true+ if a library can be loaded. A bare require() is made as
|
319
|
+
# the first attempt. If that fails, RubyGems is loaded and another attempt
|
320
|
+
# is made. If the library cannot be loaded either way, an error() is
|
321
|
+
# generated and build_report() will not be called.
|
202
322
|
#
|
203
323
|
def library_available?(library)
|
204
324
|
begin
|
@@ -214,7 +334,40 @@ module ScoutAgent
|
|
214
334
|
end
|
215
335
|
true
|
216
336
|
end
|
337
|
+
|
338
|
+
#
|
339
|
+
# Builds the data to send to the server. The public interface to this data
|
340
|
+
# lives in the helper methods: reports(), report(), add_report(), hints(),
|
341
|
+
# hint(), add_hint(), alerts(), alert(), add_alert(), errors(), error(), and
|
342
|
+
# add_error().
|
343
|
+
#
|
344
|
+
def data_for_server
|
345
|
+
@data_for_server ||= { :reports => Array.new,
|
346
|
+
:hints => Array.new,
|
347
|
+
:alerts => Array.new,
|
348
|
+
:errors => Array.new,
|
349
|
+
:memory => Hash.new }
|
350
|
+
end
|
217
351
|
|
352
|
+
#
|
353
|
+
# Builds a new alert or error Hash either by passing a Hash directly through
|
354
|
+
# or by grabbing a subject and body out of an Array of arguments.
|
355
|
+
#
|
356
|
+
def build_alert_or_error(new_alert_or_error)
|
357
|
+
if new_alert_or_error.first.is_a? Hash
|
358
|
+
new_alert_or_error
|
359
|
+
else
|
360
|
+
{:subject => new_alert_or_error.first, :body => new_alert_or_error.last}
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
364
|
+
#
|
365
|
+
# Writes generated reports and such to both the Mission database and the
|
366
|
+
# log(). The Mission memory will also be updated with the values stored
|
367
|
+
# from the execution that has just finished.
|
368
|
+
#
|
369
|
+
# Both database writes are skipped when running in the agent's test mode.
|
370
|
+
#
|
218
371
|
def write_reports_and_update_memory
|
219
372
|
%w[report hint alert error].each do |type|
|
220
373
|
Array(send("#{type}s")).each do |fields|
|
@@ -228,6 +381,6 @@ module ScoutAgent
|
|
228
381
|
end
|
229
382
|
end
|
230
383
|
|
231
|
-
# An
|
384
|
+
# An alias to support older plugins.
|
232
385
|
Plugin = Mission
|
233
386
|
end
|
data/lib/scout_agent/order.rb
CHANGED
@@ -32,8 +32,8 @@ module ScoutAgent
|
|
32
32
|
end
|
33
33
|
|
34
34
|
#
|
35
|
-
# Hooks into Ruby's
|
36
|
-
# are loaded.
|
35
|
+
# Hooks into Ruby's Class loading process so we can notice subclasses as
|
36
|
+
# they are loaded.
|
37
37
|
#
|
38
38
|
def self.inherited(order)
|
39
39
|
subclasses << order
|
data/lib/scout_agent/plan.rb
CHANGED
@@ -3,14 +3,17 @@
|
|
3
3
|
|
4
4
|
module ScoutAgent
|
5
5
|
#
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
6
|
+
# These extra methods are mixed into the OpenStruct stored in
|
7
|
+
# ScoutAgent::Plan. They add support for defaults, some interface
|
8
|
+
# improvements, support for configuration files and command-line switches,
|
9
|
+
# plus validation.
|
9
10
|
#
|
10
|
-
#
|
11
|
-
#
|
11
|
+
# To see the options stored in the plan and their current values, use the
|
12
|
+
# following command:
|
12
13
|
#
|
13
|
-
|
14
|
+
# scout_agent config
|
15
|
+
#
|
16
|
+
module Planned
|
14
17
|
################
|
15
18
|
### Defaults ###
|
16
19
|
################
|
@@ -39,31 +42,44 @@ module ScoutAgent
|
|
39
42
|
[:log_dir, nil] ]
|
40
43
|
end
|
41
44
|
|
42
|
-
#
|
45
|
+
#
|
46
|
+
# This method is used to set or reset the default configuration. It returns
|
47
|
+
# +self+ to support method chaining.
|
48
|
+
#
|
43
49
|
def set_defaults
|
44
50
|
defaults.each do |name, value|
|
45
51
|
send("#{name}=", value)
|
46
52
|
end
|
53
|
+
self # for chaining
|
47
54
|
end
|
48
55
|
alias_method :reset_defaults, :set_defaults
|
49
56
|
|
50
|
-
#
|
51
|
-
|
57
|
+
#
|
58
|
+
# --
|
59
|
+
#
|
60
|
+
# The following interface enhancements cannot use alias_method() because the
|
61
|
+
# underlying method are not defined in OpenStruct until their first use.
|
62
|
+
#
|
63
|
+
# ++
|
64
|
+
#
|
65
|
+
|
66
|
+
# Provide a more natural interface for ScoutAgent::Plan#run_as_daemon.
|
67
|
+
def run_as_daemon?
|
52
68
|
run_as_daemon
|
53
69
|
end
|
54
70
|
|
55
|
-
# Provide a more natural interface for
|
56
|
-
def test_mode?
|
71
|
+
# Provide a more natural interface for ScoutAgent::Plan#test_mode.
|
72
|
+
def test_mode?
|
57
73
|
test_mode
|
58
74
|
end
|
59
75
|
|
60
|
-
# Provide a more natural interface for
|
61
|
-
def enable_xmpp?
|
76
|
+
# Provide a more natural interface for ScoutAgent::Plan#enable_xmpp.
|
77
|
+
def enable_xmpp?
|
62
78
|
enable_xmpp
|
63
79
|
end
|
64
80
|
|
65
|
-
# Provide a more natural interface for
|
66
|
-
def periodic_snapshots?
|
81
|
+
# Provide a more natural interface for ScoutAgent::Plan#periodic_snapshots.
|
82
|
+
def periodic_snapshots?
|
67
83
|
periodic_snapshots
|
68
84
|
end
|
69
85
|
|
@@ -386,9 +402,13 @@ module ScoutAgent
|
|
386
402
|
end
|
387
403
|
end
|
388
404
|
|
405
|
+
#
|
406
|
+
# This constant holds all global configuration for the agent. There's only
|
407
|
+
# this one instance of this data and all systems share it. This data is
|
408
|
+
# configured at startup and should never again change without a restart.
|
389
409
|
#
|
390
|
-
#
|
391
|
-
#
|
410
|
+
# Users are free to add to this configuration as need (via their configuration
|
411
|
+
# file) and make use of those new values in their plugins.
|
392
412
|
#
|
393
|
-
Plan.set_defaults
|
413
|
+
Plan = OpenStruct.new.extend(Planned).set_defaults
|
394
414
|
end
|