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.
@@ -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
- class << self
7
- attr_reader :prepared
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
- def self.inherited(new_plugin)
11
- @prepared = new_plugin
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 plugin
30
+ # You can use this method to indicate one or more libraries your Mission
16
31
  # requires:
17
32
  #
18
- # class MyNeedyPlugin < Scout::Plugin
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
- # Creates a new Scout Plugin to run.
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
- # We programatically define several helper methods for creating this data.
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
- # Usage:
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
- # reports << {:data => "here"}
66
- # report(:data => "here")
67
- # add_report(:data => "here")
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
- # alerts << {:subject => "subject", :body => "body"}
70
- # alert("subject", "body")
71
- # alert(:subject => "subject", :body => "body")
72
- # add_alert("subject", "body")
73
- # add_alert(:subject => "subject", :body => "body")
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
- # errors << {:subject => "subject", :body => "body"}
76
- # error("subject", "body")
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
- %w[report hint alert error].each do |kind|
90
- class_eval <<-END
91
- def #{kind}s
92
- data_for_server[:#{kind}s]
93
- end
94
-
95
- if %w[report hint].include? "#{kind}"
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
- # Usage:
149
+ # :call-seq:
150
+ # alert(subject, body)
151
+ # alert(new_alert)
112
152
  #
113
- # memory(:no_track)
114
- # memory.delete(:no_track)
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
- # Usage:
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
- # remember(:name, value)
130
- # remember(:name1, value1, :name2, value2)
131
- # remember(:name => value)
132
- # remember(:name1 => value1, :name2 => value2)
133
- # remember(:name1, value1, :name2 => value2)
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
- hashes.each { |hash| memory.merge!(hash) }
138
- (0...other.size).step(2) { |i| memory.merge!(other[i] => other[i + 1]) }
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 is a library can be loaded. A bare require is made as the
199
- # first attempt. If that fails, RubyGems is loaded and another attempt is
200
- # made. If the library cannot be loaded either way, an error() is generated
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 external alias.
384
+ # An alias to support older plugins.
232
385
  Plugin = Mission
233
386
  end
@@ -32,8 +32,8 @@ module ScoutAgent
32
32
  end
33
33
 
34
34
  #
35
- # Hooks into Ruby's class load process so we can notice subclasses as they
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
@@ -3,14 +3,17 @@
3
3
 
4
4
  module ScoutAgent
5
5
  #
6
- # This constant holds all global configuration for the agent. There's only
7
- # this one instance of this data and all systems share it. This data is
8
- # configured at startup and should never again change without a restart.
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
- # Users are free to add to this configuration as need (via their configuration
11
- # file) and make use of those new values in their plugins.
11
+ # To see the options stored in the plan and their current values, use the
12
+ # following command:
12
13
  #
13
- class << Plan = OpenStruct.new
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
- # This method is used to set or reset the default configuration.
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
- # Provide a more natural interface for a boolean flag.
51
- def run_as_daemon? # can't use alias_method() because it's not defined
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 a boolean flag.
56
- def test_mode? # can't use alias_method() because it's not defined
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 a boolean flag.
61
- def enable_xmpp? # can't use alias_method() because it's not defined
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 a boolean flag.
66
- def periodic_snapshots? # can't use alias_method() because it's not defined
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
- # Set the defaults after we have overriden some accessors, to keep OpenStruct
391
- # from replacing them.
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