scout_agent 3.0.3 → 3.0.4
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 +10 -0
- data/Rakefile +1 -1
- data/lib/scout_agent.rb +1 -1
- data/lib/scout_agent/agent/communication_agent.rb +93 -16
- data/lib/scout_agent/database/mission_log.rb +97 -18
- data/lib/scout_agent/database/queue.rb +37 -0
- data/lib/scout_agent/database/snapshots.rb +42 -1
- data/lib/scout_agent/database/statuses.rb +25 -0
- data/lib/scout_agent/dispatcher.rb +4 -0
- data/lib/scout_agent/id_card.rb +12 -21
- data/lib/scout_agent/order.rb +17 -4
- data/lib/scout_agent/order/check_in_order.rb +11 -7
- data/lib/scout_agent/order/snapshot_order.rb +3 -21
- data/lib/scout_agent/plan.rb +34 -12
- data/lib/scout_agent/server.rb +5 -8
- data/lib/scout_agent/tracked.rb +2 -2
- data/test/tc_plan.rb +1 -1
- metadata +3 -3
data/CHANGELOG
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
== 3.0.4
|
2
|
+
|
3
|
+
* Upgraded to Arrayfields 4.7.3 to silence a warning on Ruby 1.9
|
4
|
+
* Added HTTP proxy support
|
5
|
+
* Added replies to acknowledge received messages from XMPP when a message ID is
|
6
|
+
included in the command
|
7
|
+
* Turned XMPP on and aimed it at the real Scout server
|
8
|
+
* Greatly improved error handling for the XMPP process
|
9
|
+
* Added logging and status tracking for the XMPP operations
|
10
|
+
|
1
11
|
== 3.0.3
|
2
12
|
|
3
13
|
* Upgraded to JSON 1.1.4 for Ruby 1.9 compatibility
|
data/Rakefile
CHANGED
@@ -34,7 +34,7 @@ SA_SPEC = Gem::Specification.new do |spec|
|
|
34
34
|
|
35
35
|
spec.require_path = "lib"
|
36
36
|
|
37
|
-
spec.add_dependency("arrayfields", "=4.7.
|
37
|
+
spec.add_dependency("arrayfields", "=4.7.3") # fix Amalgalite's results
|
38
38
|
spec.add_dependency("amalgalite", "=0.9.0")
|
39
39
|
spec.add_dependency("rest-client", "=0.9.2")
|
40
40
|
spec.add_dependency("json", "=1.1.4")
|
data/lib/scout_agent.rb
CHANGED
@@ -65,7 +65,7 @@ module ScoutAgent
|
|
65
65
|
end
|
66
66
|
|
67
67
|
# The version of this agent.
|
68
|
-
VERSION = "3.0.
|
68
|
+
VERSION = "3.0.4".freeze
|
69
69
|
# A Pathname reference to the agent code directory, used in dynamic loading.
|
70
70
|
LIB_DIR = Pathname.new(File.dirname(__FILE__)) + agent_name
|
71
71
|
end
|
@@ -8,6 +8,8 @@ require "scout_agent/order"
|
|
8
8
|
module ScoutAgent
|
9
9
|
class Agent
|
10
10
|
class CommunicationAgent < Agent
|
11
|
+
RECONNECT_WAIT = 60
|
12
|
+
|
11
13
|
def initialize
|
12
14
|
super # setup our log and status
|
13
15
|
|
@@ -21,16 +23,13 @@ module ScoutAgent
|
|
21
23
|
end
|
22
24
|
|
23
25
|
def run
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
else
|
32
|
-
loop { sleep 60 }
|
33
|
-
end
|
26
|
+
login
|
27
|
+
update_status("Online since #{Time.now.utc.to_db_s}")
|
28
|
+
fetch_roster
|
29
|
+
install_subscriptions_callback
|
30
|
+
install_messages_callback
|
31
|
+
listen
|
32
|
+
close_connection
|
34
33
|
end
|
35
34
|
|
36
35
|
def finish
|
@@ -46,40 +45,117 @@ module ScoutAgent
|
|
46
45
|
def login
|
47
46
|
Thread.abort_on_exception = true # make XMPP4R fail fast
|
48
47
|
@agent_jid = Jabber::JID.new("#{agent_key}@#{jabber_server}/agent")
|
48
|
+
log.info("Connecting to XMPP: #{@agent_jid}")
|
49
49
|
@jabber = Jabber::Client.new(@agent_jid)
|
50
|
-
|
51
|
-
|
50
|
+
@jabber.on_exception do |error, stream, during|
|
51
|
+
try_connection
|
52
|
+
end
|
53
|
+
try_connection
|
54
|
+
end
|
55
|
+
|
56
|
+
def try_connection
|
57
|
+
until connect_and_authenticate?
|
58
|
+
log.info( "Waiting #{RECONNECT_WAIT} seconds before making another " +
|
59
|
+
"connection attempt." )
|
60
|
+
sleep RECONNECT_WAIT
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def connect_and_authenticate?
|
65
|
+
status("Connecting")
|
66
|
+
close_connection
|
67
|
+
begin
|
68
|
+
no_warnings { @jabber.connect }
|
69
|
+
rescue Exception => error # connection failure
|
70
|
+
log.error("Failed to connect (#{error.class}: #{error.message}).")
|
71
|
+
return false
|
72
|
+
end
|
73
|
+
begin
|
74
|
+
@jabber.auth(agent_key)
|
75
|
+
rescue Jabber::ClientAuthenticationFailure # authentication failure
|
76
|
+
log.error("Authentication rejected.")
|
77
|
+
close_connection
|
78
|
+
return false
|
79
|
+
end
|
80
|
+
true
|
52
81
|
end
|
53
82
|
|
54
83
|
def update_status(message, status = nil)
|
84
|
+
status("Queuing status change")
|
55
85
|
presence = Jabber::Presence.new
|
56
86
|
presence.status = message
|
57
87
|
presence.show = status
|
58
|
-
|
88
|
+
# xmpp4r requires sending in a different Thread from the callbacks
|
89
|
+
Thread.new do
|
90
|
+
log.info("Setting status: #{message}")
|
91
|
+
begin
|
92
|
+
@jabber.send(presence)
|
93
|
+
rescue Exception # failure to update status
|
94
|
+
# do nothing: server will still see us online
|
95
|
+
log.error("Unable to update status.")
|
96
|
+
end
|
97
|
+
end
|
59
98
|
end
|
60
99
|
|
61
100
|
def fetch_roster
|
101
|
+
status("Preparing connection")
|
62
102
|
@roster = Jabber::Roster::Helper.new(@jabber)
|
63
103
|
end
|
64
104
|
|
65
105
|
def install_subscriptions_callback
|
66
106
|
@roster.add_subscription_request_callback do |_, presence|
|
107
|
+
log.info("Accepting subscription: #{presence.from}")
|
67
108
|
@roster.accept_subscription(presence.from)
|
68
109
|
end
|
69
110
|
end
|
70
111
|
|
71
112
|
def install_messages_callback
|
72
113
|
@jabber.add_message_callback do |message|
|
73
|
-
|
114
|
+
log.info("Received message from #{message.from}: #{message.body}")
|
115
|
+
status("Parsing command")
|
116
|
+
modified_body = nil
|
117
|
+
if message.body =~ /\A\s*\[\s*(\d+)\s*\]\s*(.*)/
|
118
|
+
modified_body = $2
|
119
|
+
send_chat_message(message.from, "received #{$1}")
|
120
|
+
end
|
121
|
+
if order = Order.can_handle?(message, modified_body)
|
122
|
+
status("Processing command")
|
74
123
|
order.execute
|
124
|
+
else
|
125
|
+
log.warn("Unrecognized message format, ingoring.")
|
126
|
+
end
|
127
|
+
status("Listening for commands")
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def send_chat_message(who, body)
|
132
|
+
status("Queuing message")
|
133
|
+
message = Jabber::Message.new(who, body)
|
134
|
+
message.type = :chat
|
135
|
+
# xmpp4r requires sending in a different Thread from the callbacks
|
136
|
+
Thread.new do
|
137
|
+
log.info("Sending message to #{who}: #{body}")
|
138
|
+
begin
|
139
|
+
@jabber.send(message)
|
140
|
+
rescue Exception # failure to send message
|
141
|
+
# do nothing: unable to reach the server
|
142
|
+
log.error("Unable to send message.")
|
75
143
|
end
|
76
144
|
end
|
77
145
|
end
|
78
146
|
|
79
147
|
def listen
|
148
|
+
log.info("Listening for commands.")
|
149
|
+
status("Listening for commands")
|
80
150
|
@shutdown_thread = Thread.current
|
81
151
|
Thread.stop
|
82
|
-
|
152
|
+
end
|
153
|
+
|
154
|
+
def close_connection
|
155
|
+
@jabber.close! if @jabber.is_connected?
|
156
|
+
rescue Exception # connection already closed
|
157
|
+
# do nothing: our connection is gone
|
158
|
+
log.warn("Failed to close connection.")
|
83
159
|
end
|
84
160
|
|
85
161
|
def agent_key
|
@@ -89,7 +165,8 @@ module ScoutAgent
|
|
89
165
|
end
|
90
166
|
|
91
167
|
def jabber_server
|
92
|
-
@jabber_server ||= Plan.test_mode? ? "jabber.org" :
|
168
|
+
@jabber_server ||= Plan.test_mode? ? "jabber.org" :
|
169
|
+
URI.parse(Plan.server_url).host
|
93
170
|
end
|
94
171
|
end
|
95
172
|
end
|
@@ -3,6 +3,12 @@
|
|
3
3
|
|
4
4
|
module ScoutAgent
|
5
5
|
class Database
|
6
|
+
#
|
7
|
+
# This database encapsulates the main function of the Scout agent: running
|
8
|
+
# missions. Details of the current plan and the missions of that plan are
|
9
|
+
# stored in these tables. As missions are executed, they build up reports
|
10
|
+
# which are also held here until they can be pushed to the Scout server.
|
11
|
+
#
|
6
12
|
class MissionLog < Database
|
7
13
|
#
|
8
14
|
# A default number of seconds a mission is allowed to run before it is
|
@@ -14,6 +20,10 @@ module ScoutAgent
|
|
14
20
|
# A size limit for the reports table to prevent data from building up.
|
15
21
|
REPORTS_LIMIT = 3000
|
16
22
|
|
23
|
+
#
|
24
|
+
# Build a schema for storing plans, missions, and reports. The reports
|
25
|
+
# table is size controlled by trigger to prevent infinite data growth.
|
26
|
+
#
|
17
27
|
def update_schema(version = schema_version)
|
18
28
|
case version
|
19
29
|
when 0
|
@@ -50,6 +60,14 @@ module ScoutAgent
|
|
50
60
|
end
|
51
61
|
end
|
52
62
|
|
63
|
+
################
|
64
|
+
### The Plan ###
|
65
|
+
################
|
66
|
+
|
67
|
+
#
|
68
|
+
# Returns the last known plan (+id+ and +last_modified+ date) or +nil+
|
69
|
+
# if none exists.
|
70
|
+
#
|
53
71
|
def current_plan
|
54
72
|
plan = read_from_sqlite { |sqlite|
|
55
73
|
sqlite.first_row_from(<<-END_FIND_PLAN.trim)
|
@@ -65,6 +83,13 @@ module ScoutAgent
|
|
65
83
|
nil # not found
|
66
84
|
end
|
67
85
|
|
86
|
+
#
|
87
|
+
# Given a new +last_modified+ date (as a String) and an Array of
|
88
|
+
# +missions+, this method attemps and all-or-nothing update of the current
|
89
|
+
# plan and missions. The plan and any missions that were already present
|
90
|
+
# are simply updated. New missions are added and missions no longer in
|
91
|
+
# the list are removed. New missions receive a +next_run_at+ Time of now.
|
92
|
+
#
|
68
93
|
def update_plan(last_modified, missions)
|
69
94
|
write_to_sqlite do |sqlite|
|
70
95
|
begin
|
@@ -127,6 +152,16 @@ module ScoutAgent
|
|
127
152
|
log.error("Database mission update locking error: #{error.message}.")
|
128
153
|
end
|
129
154
|
|
155
|
+
################
|
156
|
+
### Missions ###
|
157
|
+
################
|
158
|
+
|
159
|
+
#
|
160
|
+
# Returns the current mission (+id+, +timeout+, +interval+, +last_run_at+,
|
161
|
+
# +name+, +code+, +options+, and +memory+) that should be run. The
|
162
|
+
# +options+ and +memory+ fields are JSON parsed when possible. If there
|
163
|
+
# are no missions scheduled to run at this time, +nil+ is returned.
|
164
|
+
#
|
130
165
|
def current_mission
|
131
166
|
mission = read_from_sqlite { |sqlite|
|
132
167
|
return nil unless plan = current_plan
|
@@ -159,6 +194,10 @@ module ScoutAgent
|
|
159
194
|
nil # not found
|
160
195
|
end
|
161
196
|
|
197
|
+
#
|
198
|
+
# Given a +mission_id+ and a +memory+ Hash, this method updates a
|
199
|
+
# mission's stored memory.
|
200
|
+
#
|
162
201
|
def update_mission_memory(mission_id, memory)
|
163
202
|
write_to_sqlite do |sqlite|
|
164
203
|
sqlite.execute(<<-END_UPDATE_MISSION.trim, memory.to_json, mission_id)
|
@@ -170,6 +209,10 @@ module ScoutAgent
|
|
170
209
|
log.error("Database memory update error: #{error.message}.")
|
171
210
|
end
|
172
211
|
|
212
|
+
#
|
213
|
+
# Marks +mission+ as complete in the database by recording its
|
214
|
+
# +last_run_at+ Time and setting a +next_run_at+ Time.
|
215
|
+
#
|
173
216
|
def complete_mission(mission)
|
174
217
|
write_to_sqlite do |sqlite|
|
175
218
|
run_time = Time.now
|
@@ -187,7 +230,12 @@ module ScoutAgent
|
|
187
230
|
false # warn the caller that the mission will still match
|
188
231
|
end
|
189
232
|
|
233
|
+
#
|
234
|
+
# All passed mission +ids+ are reset so they will be run again at the
|
235
|
+
# first available opportunity.
|
236
|
+
#
|
190
237
|
def reset_missions(*ids)
|
238
|
+
return if ids.empty?
|
191
239
|
write_to_sqlite do |sqlite|
|
192
240
|
sqlite.execute(<<-END_RESET_MISSIONS.trim, *ids)
|
193
241
|
UPDATE missions
|
@@ -200,6 +248,38 @@ module ScoutAgent
|
|
200
248
|
log.error("Database mission reset error: #{error.message}.")
|
201
249
|
end
|
202
250
|
|
251
|
+
#
|
252
|
+
# Returns the number of seconds until another mission will be ready for
|
253
|
+
# running. If the count would be zero or less seconds, the
|
254
|
+
# +DEFAULT_INTERVAL+ is returned (in seconds) to prevent the agent from
|
255
|
+
# entering a busy loop.
|
256
|
+
#
|
257
|
+
def seconds_to_next_mission
|
258
|
+
default = DEFAULT_INTERVAL * 60
|
259
|
+
next_run_at = read_from_sqlite { |sqlite|
|
260
|
+
sqlite.first_value_from(<<-END_FIND_MISSION.trim)
|
261
|
+
SELECT next_run_at FROM missions ORDER BY next_run_at LIMIT 1
|
262
|
+
END_FIND_MISSION
|
263
|
+
}
|
264
|
+
if next_run = Time.from_db_s(next_run_at)
|
265
|
+
seconds = next_run - Time.now
|
266
|
+
seconds > 0 ? seconds : default
|
267
|
+
else
|
268
|
+
default
|
269
|
+
end
|
270
|
+
rescue Amalgalite::SQLite3::Error => error # failed to locate last run
|
271
|
+
log.error("Database next mission error: #{error.message}.")
|
272
|
+
default # use default
|
273
|
+
end
|
274
|
+
|
275
|
+
###############
|
276
|
+
### Reports ###
|
277
|
+
###############
|
278
|
+
|
279
|
+
#
|
280
|
+
# Adds a report for +mission_id+ of +type+ with +fields+ to the database.
|
281
|
+
# Returns +true+ if the write succeeded, or +false+ if it did not.
|
282
|
+
#
|
203
283
|
def write_report(mission_id, type, fields)
|
204
284
|
write_to_sqlite do |sqlite|
|
205
285
|
params = [mission_id, type, fields.to_json]
|
@@ -213,8 +293,24 @@ module ScoutAgent
|
|
213
293
|
false # couldn't be written
|
214
294
|
end
|
215
295
|
|
296
|
+
#
|
297
|
+
# This method returns an Array of all reports (+type+, +fields+,
|
298
|
+
# +created_at+, and +plugin_id+) that should be submitted to the Scout
|
299
|
+
# server. The report +fields+ will be JSON parsed when possible and
|
300
|
+
# +created_at+ is converted to a proper Time object.
|
301
|
+
#
|
302
|
+
# The act of reading these reports also triggers their removal from the
|
303
|
+
# database so we avoid sending duplicates to the server. This does mean
|
304
|
+
# that we lose data if anything goes wrong in the sending process. This
|
305
|
+
# is considered an acceptable risk, because even a
|
306
|
+
# delete-after-a-successful-send stragety is subject to duplication
|
307
|
+
# (the request might timeout but eventually complete on the server,
|
308
|
+
# for example). If anything goes wrong with the reading or deletion,
|
309
|
+
# the entire process is canceled and an empty Array is returned.
|
310
|
+
#
|
216
311
|
def current_reports
|
217
312
|
write_to_sqlite { |sqlite|
|
313
|
+
# read the current reports
|
218
314
|
begin
|
219
315
|
report_ids = Array.new
|
220
316
|
reports = query(<<-END_FIND_REPORTS.trim) { |row|
|
@@ -243,6 +339,7 @@ module ScoutAgent
|
|
243
339
|
return Array.new # return empty results
|
244
340
|
end
|
245
341
|
return reports if reports.empty?
|
342
|
+
# delete the reports we read
|
246
343
|
begin
|
247
344
|
sqlite.execute(<<-END_DELETE_REPORTS.trim, *report_ids)
|
248
345
|
DELETE FROM reports
|
@@ -260,24 +357,6 @@ module ScoutAgent
|
|
260
357
|
# try again to read reports later
|
261
358
|
log.error("Database reports locking error: #{error.message}.")
|
262
359
|
end
|
263
|
-
|
264
|
-
def seconds_to_next_mission
|
265
|
-
default = DEFAULT_INTERVAL * 60
|
266
|
-
next_run_at = read_from_sqlite { |sqlite|
|
267
|
-
sqlite.first_value_from(<<-END_FIND_MISSION.trim)
|
268
|
-
SELECT next_run_at FROM missions ORDER BY next_run_at LIMIT 1
|
269
|
-
END_FIND_MISSION
|
270
|
-
}
|
271
|
-
if next_run = Time.from_db_s(next_run_at)
|
272
|
-
seconds = next_run - Time.now
|
273
|
-
seconds > 0 ? seconds : default
|
274
|
-
else
|
275
|
-
default
|
276
|
-
end
|
277
|
-
rescue Amalgalite::SQLite3::Error => error # failed to locate last run
|
278
|
-
log.error("Database next mission error: #{error.message}.")
|
279
|
-
default # use default
|
280
|
-
end
|
281
360
|
end
|
282
361
|
end
|
283
362
|
end
|
@@ -3,10 +3,19 @@
|
|
3
3
|
|
4
4
|
module ScoutAgent
|
5
5
|
class Database
|
6
|
+
#
|
7
|
+
# This database is used to stored messages queued from external processes.
|
8
|
+
# Such messages can be data for missions or full reports ready for
|
9
|
+
# submission to the Scout server.
|
10
|
+
#
|
6
11
|
class Queue < Database
|
7
12
|
# A size limit for the queue to prevent data from building up.
|
8
13
|
QUEUE_LIMIT = 3000
|
9
14
|
|
15
|
+
#
|
16
|
+
# Builds a schema for the queue table. This table is size controlled by a
|
17
|
+
# trigger to prevent infinite data growth.
|
18
|
+
#
|
10
19
|
def update_schema(version = schema_version)
|
11
20
|
case version
|
12
21
|
when 0
|
@@ -24,6 +33,14 @@ module ScoutAgent
|
|
24
33
|
end
|
25
34
|
end
|
26
35
|
|
36
|
+
#
|
37
|
+
# Adds a message to the queue. The passed +mission_id+ needs to be an
|
38
|
+
# Integer ID for a mission or one of the Strings <tt>'report'</tt>,
|
39
|
+
# <tt>'hint'</tt>, <tt>'alert'</tt>, or <tt>'error'</tt> for a full
|
40
|
+
# report. The +fields+ parameter is expected to be a Hash of fields and
|
41
|
+
# should include a <tt>'plugin_id'</tt> key identifying what the data is
|
42
|
+
# for when the message is a report for the server.
|
43
|
+
#
|
27
44
|
def enqueue(mission_id, fields)
|
28
45
|
write_to_sqlite do |sqlite|
|
29
46
|
sqlite.execute(<<-END_ENQUEUE.trim, mission_id, fields.to_json)
|
@@ -36,6 +53,12 @@ module ScoutAgent
|
|
36
53
|
false # reject bad message
|
37
54
|
end
|
38
55
|
|
56
|
+
#
|
57
|
+
# Returns a message (+id+, +fields+, and +created_at+) queued for
|
58
|
+
# +mission_id+. The +fields+ are JSON parsed if possible and +created_at+
|
59
|
+
# is converted to a Time object. This method will return +nil+ if no
|
60
|
+
# messages are queued for +mission_id+.
|
61
|
+
#
|
39
62
|
def peek(mission_id)
|
40
63
|
queued = read_from_sqlite { |sqlite|
|
41
64
|
sqlite.first_row_from(<<-END_FIND_QUEUED.trim, mission_id.to_s)
|
@@ -63,8 +86,16 @@ module ScoutAgent
|
|
63
86
|
nil # not found
|
64
87
|
end
|
65
88
|
|
89
|
+
#
|
90
|
+
# This method returns queued reports intended for the Scout server.
|
91
|
+
#
|
92
|
+
# The process is pretty much identical to how mission generated reports
|
93
|
+
# are pulled. See ScoutAgent::Database::MissionLog#current_reports() for
|
94
|
+
# details.
|
95
|
+
#
|
66
96
|
def queued_reports
|
67
97
|
write_to_sqlite { |sqlite|
|
98
|
+
# read the current reports
|
68
99
|
begin
|
69
100
|
report_ids = Array.new
|
70
101
|
reports = query(<<-END_FIND_REPORTS.trim) { |row|
|
@@ -95,6 +126,7 @@ module ScoutAgent
|
|
95
126
|
return Array.new # return empty results
|
96
127
|
end
|
97
128
|
return reports if reports.empty?
|
129
|
+
# delete the reports we read
|
98
130
|
unless dequeue(*report_ids)
|
99
131
|
# cancel sending this batch
|
100
132
|
sqlite.rollback # we can't submit unless we're sure they are gone
|
@@ -107,7 +139,12 @@ module ScoutAgent
|
|
107
139
|
log.error("Database queued reports locking error: #{error.message}.")
|
108
140
|
end
|
109
141
|
|
142
|
+
#
|
143
|
+
# Removes queued messages from the database by their +ids+. Returns
|
144
|
+
# +true+ if the removal succeeded, or +false+ otherwise.
|
145
|
+
#
|
110
146
|
def dequeue(*ids)
|
147
|
+
return true if ids.empty?
|
111
148
|
write_to_sqlite do |sqlite|
|
112
149
|
sqlite.execute(<<-END_DELETE_QUEUED.trim, *ids)
|
113
150
|
DELETE FROM queue WHERE ROWID IN (#{(['?'] * ids.size).join(', ')})
|
@@ -3,6 +3,12 @@
|
|
3
3
|
|
4
4
|
module ScoutAgent
|
5
5
|
class Database
|
6
|
+
#
|
7
|
+
# This database holds snapshot commands and results. These commands are
|
8
|
+
# used as a way of recording the current state of the box the agent runs on.
|
9
|
+
# The results of these commands may help identify causes of problems
|
10
|
+
# reported by missions the agent runs.
|
11
|
+
#
|
6
12
|
class Snapshots < Database
|
7
13
|
# A maximum time in seconds after which a command run is halted.
|
8
14
|
DEFAULT_TIMEOUT = 60
|
@@ -14,6 +20,11 @@ module ScoutAgent
|
|
14
20
|
# A size limit for the runs table to prevent data from building up.
|
15
21
|
RUNS_LIMIT = 3000
|
16
22
|
|
23
|
+
#
|
24
|
+
# Builds a schema for tables holding commands and the runs of those
|
25
|
+
# commands. The runs table is size controlled via a trigger to prevent
|
26
|
+
# infinite data growth.
|
27
|
+
#
|
17
28
|
def update_schema(version = schema_version)
|
18
29
|
case version
|
19
30
|
when 0
|
@@ -39,7 +50,12 @@ module ScoutAgent
|
|
39
50
|
END_INITIAL_SCHEMA
|
40
51
|
end
|
41
52
|
end
|
42
|
-
|
53
|
+
|
54
|
+
#
|
55
|
+
# Updates the list of snapshot +commands+ in the database. Existing
|
56
|
+
# commands are updated, new commands are added, and commands no longer in
|
57
|
+
# the list are deleted.
|
58
|
+
#
|
43
59
|
def update_commands(commands)
|
44
60
|
write_to_sqlite do |sqlite|
|
45
61
|
codes = commands.map { |m| m["code"] }
|
@@ -83,6 +99,11 @@ module ScoutAgent
|
|
83
99
|
log.error("Database command update locking error: #{error.message}.")
|
84
100
|
end
|
85
101
|
|
102
|
+
#
|
103
|
+
# Returns all current commands (+id+, +timeout+, +interval+,
|
104
|
+
# +last_run_at+, and +code+) that should be executed as part of the
|
105
|
+
# current snapshot. An empty Array is returned if no commands are found.
|
106
|
+
#
|
86
107
|
def current_commands
|
87
108
|
query(<<-END_FIND_COMMANDS.trim, Time.now.to_db_s) { |row|
|
88
109
|
SELECT ROWID AS id, timeout, interval, last_run_at, code
|
@@ -96,6 +117,11 @@ module ScoutAgent
|
|
96
117
|
Array.new # return empty results
|
97
118
|
end
|
98
119
|
|
120
|
+
#
|
121
|
+
# Returns +true+ or +false+ to indicate if any commands are stored in the
|
122
|
+
# snapshot database. If the database is inaccessible and the answer
|
123
|
+
# cannot be determined, +nil+ is returned.
|
124
|
+
#
|
99
125
|
def have_commands?
|
100
126
|
read_from_sqlite { |sqlite|
|
101
127
|
!!sqlite.first_value_from("SELECT ROWID FROM commands LIMIT 1")
|
@@ -105,8 +131,14 @@ module ScoutAgent
|
|
105
131
|
nil # commands not found
|
106
132
|
end
|
107
133
|
|
134
|
+
#
|
135
|
+
# Marks +command+ as just having run in the database and updates its
|
136
|
+
# +next_run_at+ Time. A run is also created for the +command+ documenting
|
137
|
+
# its +output+, +exit_status+, +snapshot_at+ Time, and +run_time+.
|
138
|
+
#
|
108
139
|
def complete_run(command, output, exit_status, snapshot_at, run_time)
|
109
140
|
write_to_sqlite do |sqlite|
|
141
|
+
# record run
|
110
142
|
params = [ command[:code],
|
111
143
|
output,
|
112
144
|
exit_status,
|
@@ -123,6 +155,7 @@ module ScoutAgent
|
|
123
155
|
log.error( "Database bad command run (#{command[:code]}) error: " +
|
124
156
|
"#{error.message}." )
|
125
157
|
end
|
158
|
+
# update command
|
126
159
|
run_time = Time.now
|
127
160
|
params = [ run_time.to_db_s,
|
128
161
|
( run_time +
|
@@ -143,8 +176,15 @@ module ScoutAgent
|
|
143
176
|
log.error("Database complete command locking error: #{error.message}.")
|
144
177
|
end
|
145
178
|
|
179
|
+
#
|
180
|
+
# This method returns command runs intended for the Scout server.
|
181
|
+
#
|
182
|
+
# The process is very similar to how mission generated reports are pulled.
|
183
|
+
# See ScoutAgent::Database::MissionLog#current_reports() for details.
|
184
|
+
#
|
146
185
|
def current_runs
|
147
186
|
write_to_sqlite { |sqlite|
|
187
|
+
# read the current runs
|
148
188
|
begin
|
149
189
|
run_ids = Array.new
|
150
190
|
runs = query(<<-END_FIND_RUNS.trim) { |row|
|
@@ -166,6 +206,7 @@ module ScoutAgent
|
|
166
206
|
return Array.new # return empty results
|
167
207
|
end
|
168
208
|
return runs if runs.empty?
|
209
|
+
# delete the runs we read
|
169
210
|
begin
|
170
211
|
sqlite.execute(<<-END_DELETE_RUNS.trim, *run_ids)
|
171
212
|
DELETE FROM runs
|
@@ -3,7 +3,13 @@
|
|
3
3
|
|
4
4
|
module ScoutAgent
|
5
5
|
class Database
|
6
|
+
#
|
7
|
+
# This small database keeps track of the current status of all running
|
8
|
+
# processes withing the agent. This allows tracking of process function
|
9
|
+
# (represented as the process name), ID, and current task.
|
10
|
+
#
|
6
11
|
class Statuses < Database
|
12
|
+
# Builds a schema for the process statuses table.
|
7
13
|
def update_schema(version = schema_version)
|
8
14
|
case version
|
9
15
|
when 0
|
@@ -21,6 +27,11 @@ module ScoutAgent
|
|
21
27
|
end
|
22
28
|
end
|
23
29
|
|
30
|
+
#
|
31
|
+
# Record the current +status+ for the calling process. The process ID is
|
32
|
+
# determined with <tt>Process::pid()</tt> and +name+ is pulled from
|
33
|
+
# <tt>IDCard::me()#process_name()</tt> when available.
|
34
|
+
#
|
24
35
|
def update_status(status, name = IDCard.me && IDCard.me.process_name)
|
25
36
|
write_to_sqlite do |sqlite|
|
26
37
|
sqlite.execute(<<-END_UPDATE_STATUS.trim, name, Process.pid, status)
|
@@ -33,6 +44,11 @@ module ScoutAgent
|
|
33
44
|
log.error("Database status update error: #{error.message}.")
|
34
45
|
end
|
35
46
|
|
47
|
+
#
|
48
|
+
# Removes the status record for +name+ (pulled from
|
49
|
+
# <tt>IDCard::me()#process_name()</tt> by default). This is generally
|
50
|
+
# done by processes in an <tt>at_exit()</tt> block.
|
51
|
+
#
|
36
52
|
def clear_status(name = IDCard.me && IDCard.me.process_name)
|
37
53
|
write_to_sqlite do |sqlite|
|
38
54
|
sqlite.execute("DELETE FROM statuses WHERE name = ?", name)
|
@@ -42,6 +58,11 @@ module ScoutAgent
|
|
42
58
|
log.error("Database status clearing error: #{error.message}.")
|
43
59
|
end
|
44
60
|
|
61
|
+
#
|
62
|
+
# Returns the current statuses (+name+, +pid+, +status+,
|
63
|
+
# and +last_updated_at+) for all known processes. An empty Array is
|
64
|
+
# returned if no processes are currently active.
|
65
|
+
#
|
45
66
|
def current_statuses
|
46
67
|
query(<<-END_FIND_STATUSES.trim)
|
47
68
|
SELECT name, pid, status, last_updated_at FROM statuses ORDER BY ROWID
|
@@ -51,6 +72,10 @@ module ScoutAgent
|
|
51
72
|
Array.new # return empty results
|
52
73
|
end
|
53
74
|
|
75
|
+
#
|
76
|
+
# Returns the current +status+ message for +name+ (pulled from
|
77
|
+
# <tt>IDCard::me()#process_name()</tt> by default).
|
78
|
+
#
|
54
79
|
def current_status(name = IDCard.me && IDCard.me.process_name)
|
55
80
|
read_from_sqlite { |sqlite|
|
56
81
|
sqlite.first_value_from(<<-END_FIND_STATUS, name)
|
@@ -32,6 +32,10 @@ module ScoutAgent
|
|
32
32
|
"The URL for the server to report to." ) do |url|
|
33
33
|
switches[:server_url] = url
|
34
34
|
end
|
35
|
+
opts.on( "-p", "--proxy URL", String,
|
36
|
+
"A proxy URL to pass HTTP requests through." ) do |url|
|
37
|
+
switches[:proxy_url] = url
|
38
|
+
end
|
35
39
|
opts.on( "-d", "--[no-]daemon",
|
36
40
|
"Run in the background as a daemon." ) do |boolean|
|
37
41
|
switches[:run_as_daemon] = boolean
|
data/lib/scout_agent/id_card.rb
CHANGED
@@ -41,16 +41,17 @@ module ScoutAgent
|
|
41
41
|
Plan.pid_dir + "#{@process_name}.pid"
|
42
42
|
end
|
43
43
|
|
44
|
-
# Returns the PID for the named process
|
44
|
+
# Returns the PID for the named process or +nil+ if it cannot be read.
|
45
45
|
def pid
|
46
46
|
pid_file.read.to_i
|
47
|
-
rescue Exception
|
47
|
+
rescue Exception # cannot read file
|
48
48
|
nil
|
49
49
|
end
|
50
50
|
|
51
51
|
#
|
52
52
|
# Tries to send +message+ as a signal to the process represented by this
|
53
|
-
# instance. You can pass any message Process.kill() would
|
53
|
+
# instance. You can pass any message <tt>Process.kill()</tt> would
|
54
|
+
# understand.
|
54
55
|
#
|
55
56
|
# Returns +true+ if the signal was sent, or +false+ if the PID file could
|
56
57
|
# not be read. Any Exception raised during the send, such as Errno::ESRCH
|
@@ -71,9 +72,9 @@ module ScoutAgent
|
|
71
72
|
# stale claims are ignored and replaced, if possible.
|
72
73
|
#
|
73
74
|
# This method returns +true+ in the claim succeeded and +false+ if it could
|
74
|
-
# not happen for any reason. A return of +true+ indicates that
|
75
|
-
# been updated and an exit handle has been
|
76
|
-
# as the process ends.
|
75
|
+
# not happen for any reason. A return of +true+ indicates that
|
76
|
+
# <tt>IDCard::me()</tt> has been updated and an exit handle has been
|
77
|
+
# installed to revoke() this claim as the process ends.
|
77
78
|
#
|
78
79
|
def authorize
|
79
80
|
File.open(pid_file, File::CREAT | File::EXCL | File::WRONLY) do |pid|
|
@@ -91,9 +92,7 @@ module ScoutAgent
|
|
91
92
|
self.class.me = self
|
92
93
|
|
93
94
|
at_my_exit do
|
94
|
-
|
95
|
-
# log.error "Unable to unlink pid file: #{$!.message}" if log
|
96
|
-
end
|
95
|
+
revoke
|
97
96
|
end
|
98
97
|
true
|
99
98
|
rescue Errno::EEXIST # pid_file already exists
|
@@ -101,32 +100,24 @@ module ScoutAgent
|
|
101
100
|
if pid.flock(File::LOCK_EX | File::LOCK_NB)
|
102
101
|
if pid.read =~ /\A\d+/
|
103
102
|
begin
|
104
|
-
|
105
|
-
# log.warn "Could not create or read PID file. " +
|
106
|
-
# "You may need to the path to the config directory. " +
|
107
|
-
# "See: http://scoutapp.com/help#data_file" if log
|
108
|
-
end
|
103
|
+
signal(0) # check for the existing process
|
109
104
|
rescue Errno::ESRCH # no such process
|
110
|
-
#
|
105
|
+
# stale PID file found, clearing it and reloading
|
111
106
|
if revoke
|
112
107
|
pid.flock(File::LOCK_UN) # release the lock before we recurse
|
113
108
|
return authorize # try again
|
114
|
-
else
|
115
|
-
# log.info "Failed to clear PID." if log
|
116
109
|
end
|
117
110
|
rescue Errno::EACCES # don't have permission
|
118
111
|
# nothing we can do so give up
|
119
112
|
end
|
120
|
-
else
|
121
|
-
# nothing we can do so give up
|
122
113
|
end
|
123
114
|
pid.flock(File::LOCK_UN) # release the lock
|
124
115
|
else
|
125
|
-
#
|
116
|
+
# couldn't grab a file lock to verify existing PID file
|
126
117
|
return false
|
127
118
|
end
|
128
119
|
end
|
129
|
-
#
|
120
|
+
# process was already running
|
130
121
|
false
|
131
122
|
end
|
132
123
|
|
data/lib/scout_agent/order.rb
CHANGED
@@ -28,16 +28,22 @@ module ScoutAgent
|
|
28
28
|
subclasses
|
29
29
|
end
|
30
30
|
|
31
|
-
def self.can_handle?(message)
|
31
|
+
def self.can_handle?(message, modified_body = nil)
|
32
32
|
subclasses.each do |order|
|
33
|
-
if match_details = order.match?(message)
|
33
|
+
if match_details = order.match?(message, modified_body)
|
34
34
|
return order.new(message, match_details)
|
35
35
|
end
|
36
36
|
end
|
37
|
+
nil
|
37
38
|
end
|
38
39
|
|
39
|
-
def self.match?(message)
|
40
|
-
const_defined?(:MATCH_RE) and
|
40
|
+
def self.match?(message, modified_body = nil)
|
41
|
+
const_defined?(:MATCH_RE) and
|
42
|
+
const_get(:MATCH_RE).match(modified_body || message.body)
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.master_agent
|
46
|
+
@master_agent ||= IDCard.new(:master)
|
41
47
|
end
|
42
48
|
|
43
49
|
def initialize(message, match_details)
|
@@ -55,5 +61,12 @@ module ScoutAgent
|
|
55
61
|
raise NotImplementedError,
|
56
62
|
"Subclasses must override ScoutAgent::Order#execute()."
|
57
63
|
end
|
64
|
+
|
65
|
+
def notify_master
|
66
|
+
self.class.master_agent.signal("ALRM")
|
67
|
+
rescue Exception # unable to signal process
|
68
|
+
# do nothing: process will catch up when it restarts
|
69
|
+
log.warn("Unable to signal master process.")
|
70
|
+
end
|
58
71
|
end
|
59
72
|
end
|
@@ -15,19 +15,23 @@ module ScoutAgent
|
|
15
15
|
@mission_log = db
|
16
16
|
end
|
17
17
|
|
18
|
-
def self.master_agent
|
19
|
-
@master_agent ||= IDCard.new(:master)
|
20
|
-
end
|
21
|
-
|
22
18
|
def execute
|
23
19
|
if id_str = match_details.captures.first
|
24
20
|
ids = id_str.scan(/\d+/).map { |n| n.to_i }
|
25
21
|
unless ids.empty?
|
26
|
-
|
22
|
+
s = ids.size == 1 ? "" : "s"
|
23
|
+
ids_str = case ids.size
|
24
|
+
when 1 then ids.first.to_s
|
25
|
+
when 2 then ids.join(" and ")
|
26
|
+
else ids[0..-2].join(", ") + ", and #{ids.last}"
|
27
|
+
end
|
28
|
+
log.info("Clearing wait time#{s} for mission#{s} (#{ids_str}).")
|
29
|
+
self.class.mission_log.reset_missions(*ids)
|
27
30
|
end
|
28
31
|
end
|
29
|
-
|
32
|
+
log.info("Requesting an immediate check-in.")
|
33
|
+
notify_master
|
30
34
|
end
|
31
35
|
end
|
32
36
|
end
|
33
|
-
end
|
37
|
+
end
|
@@ -4,30 +4,12 @@
|
|
4
4
|
module ScoutAgent
|
5
5
|
class Order
|
6
6
|
class SnapshotOrder < Order
|
7
|
-
MATCH_RE = /\A\s*snap[-_]?shot
|
8
|
-
|
9
|
-
def self.snapshots
|
10
|
-
return @snapshots if defined? @snapshots
|
11
|
-
unless db = Database.load(:snapshots, log)
|
12
|
-
log.fatal("Could not load snapshots database.")
|
13
|
-
exit
|
14
|
-
end
|
15
|
-
@snapshots = db
|
16
|
-
end
|
17
|
-
|
18
|
-
def self.master_agent
|
19
|
-
@master_agent ||= IDCard.new(:master)
|
20
|
-
end
|
7
|
+
MATCH_RE = /\A\s*snap[-_]?shot\s*\z/
|
21
8
|
|
22
9
|
def execute
|
23
|
-
|
24
|
-
# ids = id_str.scan(/\d+/).map { |n| n.to_i }
|
25
|
-
# unless ids.empty?
|
26
|
-
# self.class.mission_log.reset_missions(ids)
|
27
|
-
# end
|
28
|
-
# end
|
10
|
+
log.info("Requesting that a snapshot be taken.")
|
29
11
|
API.take_snapshot
|
30
|
-
|
12
|
+
notify_master
|
31
13
|
end
|
32
14
|
end
|
33
15
|
end
|
data/lib/scout_agent/plan.rb
CHANGED
@@ -18,6 +18,7 @@ module ScoutAgent
|
|
18
18
|
# The default configuration for this agent.
|
19
19
|
def defaults
|
20
20
|
[ [:server_url, "http://beta.scoutapp.com:3000"],
|
21
|
+
[:proxy_url, nil],
|
21
22
|
[:run_as_daemon, true],
|
22
23
|
[:logging_level, "INFO"],
|
23
24
|
[:test_mode, false],
|
@@ -37,7 +38,9 @@ module ScoutAgent
|
|
37
38
|
|
38
39
|
# This method is used to set or reset the default configuration.
|
39
40
|
def set_defaults
|
40
|
-
defaults.each
|
41
|
+
defaults.each do |name, value|
|
42
|
+
send("#{name}=", value)
|
43
|
+
end
|
41
44
|
end
|
42
45
|
alias_method :reset_defaults, :set_defaults
|
43
46
|
|
@@ -74,6 +77,7 @@ module ScoutAgent
|
|
74
77
|
config_file.open(File::CREAT|File::EXCL|File::WRONLY) do |pid|
|
75
78
|
pid.puts <<-END_DEFAULT_CONFIG.trim
|
76
79
|
#!/usr/bin/env ruby -wKU
|
80
|
+
# encoding: UTF-8
|
77
81
|
|
78
82
|
#
|
79
83
|
# This file configures #{ScoutAgent.proper_agent_name}. The settings in
|
@@ -92,6 +96,12 @@ module ScoutAgent
|
|
92
96
|
|
93
97
|
# The following is the URL used to reach the server:
|
94
98
|
config.server_url = #{server_url.inspect}
|
99
|
+
#
|
100
|
+
# Set the following if your HTTP requests need to go through a proxy.
|
101
|
+
# All non-XMPP requests between the agent and the server will go through
|
102
|
+
# this URL.
|
103
|
+
#
|
104
|
+
config.proxy_url = #{proxy_url.inspect}
|
95
105
|
|
96
106
|
#
|
97
107
|
# When the following is true, the agent will disconnect from the
|
@@ -139,8 +149,8 @@ module ScoutAgent
|
|
139
149
|
# Note: to have the prefix effect the location of this file or
|
140
150
|
# to set your OS's configuration path, you will need to use
|
141
151
|
# command-line switches (--prefix and --os-config-path respectively).
|
142
|
-
# It wouldn't help to have those settings in this file
|
143
|
-
#
|
152
|
+
# It wouldn't help to have those settings in this file as they are
|
153
|
+
# needed before this file is loaded.
|
144
154
|
#
|
145
155
|
config.prefix_path = #{prefix_path.to_s.inspect}
|
146
156
|
|
@@ -161,11 +171,11 @@ module ScoutAgent
|
|
161
171
|
# try to switch to. Choices are tried from left to right, so by
|
162
172
|
# default the "daemon" user is used but the agent will try
|
163
173
|
# "nobody" if daemon is not available. These are standard users
|
164
|
-
# and groups for
|
174
|
+
# and groups for long running processes on Unix.
|
165
175
|
#
|
166
176
|
# If you wish to improve security even more, we recommend creating
|
167
177
|
# a user and group called "#{ScoutAgent.agent_name}" and replacing these
|
168
|
-
# defaults with what you created. This puts you full control
|
178
|
+
# defaults with what you created. This puts you in full control
|
169
179
|
# of what the agent will have access to on your system and makes
|
170
180
|
# it easier to track what the agent is doing.
|
171
181
|
#
|
@@ -193,7 +203,9 @@ module ScoutAgent
|
|
193
203
|
|
194
204
|
# This method allows mass update through a +hash+ of settings.
|
195
205
|
def update_from_switches(hash)
|
196
|
-
hash.each
|
206
|
+
hash.each do |name, value|
|
207
|
+
send("#{name}=", value)
|
208
|
+
end
|
197
209
|
end
|
198
210
|
alias_method :update_from_hash, :update_from_switches
|
199
211
|
|
@@ -211,9 +223,9 @@ module ScoutAgent
|
|
211
223
|
# These paths prefix all of the specific application paths.
|
212
224
|
#
|
213
225
|
%w[os_config_path os_db_path os_pid_path os_log_path].each do |path|
|
214
|
-
define_method(path)
|
226
|
+
define_method(path) {
|
215
227
|
prefix_path + super()
|
216
|
-
|
228
|
+
}
|
217
229
|
end
|
218
230
|
|
219
231
|
#
|
@@ -230,9 +242,9 @@ module ScoutAgent
|
|
230
242
|
# are named after the agent.
|
231
243
|
#
|
232
244
|
%w[db pid log].each do |path|
|
233
|
-
define_method("#{path}_dir")
|
245
|
+
define_method("#{path}_dir") {
|
234
246
|
send("os_#{path}_path") + (super() || ScoutAgent.agent_name)
|
235
|
-
|
247
|
+
}
|
236
248
|
end
|
237
249
|
|
238
250
|
#
|
@@ -243,7 +255,7 @@ module ScoutAgent
|
|
243
255
|
{ :db_dir => 0777,
|
244
256
|
:pid_dir => 0775,
|
245
257
|
:log_dir => 0777 }.each do |path, permissions|
|
246
|
-
define_method("build_#{path}")
|
258
|
+
define_method("build_#{path}") { |group_id|
|
247
259
|
begin
|
248
260
|
send(path).mkpath
|
249
261
|
send(path).chown(nil, group_id)
|
@@ -252,13 +264,17 @@ module ScoutAgent
|
|
252
264
|
rescue Errno::EACCES, Errno::EPERM # don't have permission
|
253
265
|
false
|
254
266
|
end
|
255
|
-
|
267
|
+
}
|
256
268
|
end
|
257
269
|
|
258
270
|
#############
|
259
271
|
### URL's ###
|
260
272
|
#############
|
261
273
|
|
274
|
+
#
|
275
|
+
# Returns the full URL for check-ins by this agent. This is the
|
276
|
+
# +server_url+ plus the agent path and the key specific to this agent.
|
277
|
+
#
|
262
278
|
def agent_url
|
263
279
|
URI.join(server_url, "clients/#{agent_key}")
|
264
280
|
end
|
@@ -267,6 +283,12 @@ module ScoutAgent
|
|
267
283
|
### Databases ###
|
268
284
|
#################
|
269
285
|
|
286
|
+
#
|
287
|
+
# This method is used to prepare a database that can be accessed by any
|
288
|
+
# process. The database is first loaded by +name+. Then the permissions
|
289
|
+
# on that database are changed to allow all processes to read and write to
|
290
|
+
# the created database.
|
291
|
+
#
|
270
292
|
def prepare_global_database(name)
|
271
293
|
db = Database.load(name) or return false
|
272
294
|
db.path.chmod(0777)
|
data/lib/scout_agent/server.rb
CHANGED
@@ -18,6 +18,8 @@ module ScoutAgent
|
|
18
18
|
:headers => { :client_version => ScoutAgent::VERSION,
|
19
19
|
:accept_encoding => "gzip" }
|
20
20
|
)
|
21
|
+
# make sure proxy is set, if needed
|
22
|
+
RestClient.proxy = Plan.proxy_url
|
21
23
|
end
|
22
24
|
|
23
25
|
# The log connection notes will be written to.
|
@@ -40,13 +42,8 @@ module ScoutAgent
|
|
40
42
|
log.warn("Plan was malformed zipped data.")
|
41
43
|
"" # replace bad plan with empty plan
|
42
44
|
rescue RestClient::RequestFailed => error # RestClient bug workaround
|
43
|
-
|
44
|
-
|
45
|
-
error.response.body # the returned plan
|
46
|
-
else
|
47
|
-
log.warn("Plan was returned as a failure code: #{error.http_code}.")
|
48
|
-
nil # failed to retrieve plan
|
49
|
-
end
|
45
|
+
log.warn("Plan was returned as a failure code: #{error.http_code}.")
|
46
|
+
nil # failed to retrieve plan
|
50
47
|
rescue RestClient::NotModified
|
51
48
|
log.info("Plan was not modified.")
|
52
49
|
"" # empty plan
|
@@ -61,7 +58,7 @@ module ScoutAgent
|
|
61
58
|
# as much as possible.
|
62
59
|
#
|
63
60
|
# If the ckeck-in succeeds, +true+ is returned. However, +false+ doesn't
|
64
|
-
# ensure that we failed. RestClient may
|
61
|
+
# ensure that we failed. RestClient may timeout a long connection attempt,
|
65
62
|
# but the server may still complete it eventually.
|
66
63
|
#
|
67
64
|
def post_checkin(data)
|
data/lib/scout_agent/tracked.rb
CHANGED
@@ -38,7 +38,7 @@ module ScoutAgent
|
|
38
38
|
# status(status, name = IDCard.me && IDCard.me.process_name)
|
39
39
|
#
|
40
40
|
# Sets the +status+ of this process. You can pass +name+ explicitly if it
|
41
|
-
# cannot be properly determined from <tt>IDCard
|
41
|
+
# cannot be properly determined from <tt>IDCard::me()#process_name()</tt>.
|
42
42
|
#
|
43
43
|
def status(*args)
|
44
44
|
status_database.update_status(*args) if status_database
|
@@ -51,7 +51,7 @@ module ScoutAgent
|
|
51
51
|
# Clears any status currently set for this process. This should be called
|
52
52
|
# for any process setting status messages <tt>at_exit()</tt>. You can pass
|
53
53
|
# +name+ explicitly if it cannot be properly determined from
|
54
|
-
# <tt>IDCard
|
54
|
+
# <tt>IDCard::me()#process_name()</tt>.
|
55
55
|
#
|
56
56
|
def clear_status(*args)
|
57
57
|
status_database.clear_status(*args) if status_database
|
data/test/tc_plan.rb
CHANGED
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.4
|
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-13 00:00:00 -05:00
|
16
16
|
default_executable:
|
17
17
|
dependencies:
|
18
18
|
- !ruby/object:Gem::Dependency
|
@@ -23,7 +23,7 @@ dependencies:
|
|
23
23
|
requirements:
|
24
24
|
- - "="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 4.7.
|
26
|
+
version: 4.7.3
|
27
27
|
version:
|
28
28
|
- !ruby/object:Gem::Dependency
|
29
29
|
name: amalgalite
|