scout_agent 3.0.6 → 3.0.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -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 NAME1,NAME2,...", Array,
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
@@ -3,8 +3,8 @@
3
3
 
4
4
  module ScoutAgent
5
5
  class Lifeline
6
- NO_CONTACT_TIMEOUT = 3
7
- CHECK_IN_FREQUENCY = 0.99 # gives us three check ins before a cutoff
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)
@@ -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
- @mission_log_db = Database.load(:mission_log)
42
- @queue_db = Database.load(:queue)
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
- while message = @queue_db.peek(@id)
136
- if block.arity == 1
137
- block[message[:fields]]
138
- else
139
- block[message[:fields], message[:created_at]]
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
- @mission_log_db.update_mission_memory(@id, data_for_server[:memory])
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)
@@ -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 do |order|
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
- end
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
- attr_reader :message, :match_details
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
- Order.log
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
@@ -17,7 +17,7 @@ module ScoutAgent
17
17
 
18
18
  # The default configuration for this agent.
19
19
  def defaults
20
- [ [:server_url, "http://beta.scoutapp.com:3000"],
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.6".freeze
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.6
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-17 00:00:00 -05:00
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: Scout is a full server monitoring solution. You can install standard plugins to get started with basic monitoring right away, or build your own plugins to address your specific needs. Scout can be tied into any monitoring strategy, providing you data collection, trend analysis, email notifications and more.
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.1
185
+ rubygems_version: 1.3.2
178
186
  signing_key:
179
- specification_version: 2
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