Class: ScoutAgent::Database::Snapshots
- Inherits:
-
ScoutAgent::Database
- Object
- ScoutAgent::Database
- ScoutAgent::Database::Snapshots
- Defined in:
- lib/scout_agent/database/snapshots.rb
Overview
This database holds snapshot commands and results. These commands are used as a way of recording the current state of the box the agent runs on. The results of these commands may help identify causes of problems reported by missions the agent runs.
Constant Summary collapse
- DEFAULT_TIMEOUT =
A maximum time in seconds after which a command run is halted.
60
- DEFAULT_INTERVAL =
A minimum time in minutes that must pass before a command will be run again in the next snapshot.
10
- RUNS_LIMIT =
A size limit for the runs table to prevent data from building up.
3000
Instance Attribute Summary
Attributes inherited from ScoutAgent::Database
Instance Method Summary collapse
-
#complete_run(command, output, exit_status, snapshot_at, run_time) ⇒ Object
Marks
command
as just having run in the database and updates itsnext_run_at
Time. -
#current_commands ⇒ Object
Returns all current commands (
id
,timeout
,interval
,last_run_at
, andcode
) that should be executed as part of the current snapshot. -
#current_runs ⇒ Object
This method returns command runs intended for the Scout server.
-
#have_commands? ⇒ Boolean
Returns
true
orfalse
to indicate if any commands are stored in the snapshot database. -
#reset_all_commands ⇒ Object
All commands are reset so they will be run again at the first available opportunity.
-
#update_commands(commands) ⇒ Object
Updates the list of snapshot
commands
in the database. -
#update_schema(version = schema_version) ⇒ Object
Builds a schema for tables holding commands and the runs of those commands.
Methods inherited from ScoutAgent::Database
#initialize, load, #locked?, #maintain, #migrate, #path, path, #prepare_connection, #query, #read_from_sqlite, #read_locked?, #schema_version, #write_locked?, #write_to_sqlite
Constructor Details
This class inherits a constructor from ScoutAgent::Database
Instance Method Details
#complete_run(command, output, exit_status, snapshot_at, run_time) ⇒ Object
Marks command
as just having run in the database and updates its next_run_at
Time. A run is also created for the command
documenting its output
, exit_status
, snapshot_at
Time, and run_time
.
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 |
# File 'lib/scout_agent/database/snapshots.rb', line 163 def complete_run(command, output, exit_status, snapshot_at, run_time) write_to_sqlite do |sqlite| # record run params = [ command[:code], output, exit_status, snapshot_at.to_db_s, run_time ] begin sqlite.execute(<<-END_INSERT_RUN.trim, *params) INSERT INTO runs( code, output, exit_status, snapshot_at, run_time ) VALUES( ?, ?, ?, ?, ? ) END_INSERT_RUN rescue Amalgalite::SQLite3::Error => error # failed to add run # do nothing: skip bad command and move on log.error( "Database bad command run (#{command[:code]}) error: " + "#{error.}." ) end # update command run_time = Time.now params = [ run_time.to_db_s, ( run_time + command[:interval] * 60 ).to_db_s(:trim_seconds), command[:id] ] begin sqlite.execute(<<-END_UPDATE_COMMAND.trim, *params) UPDATE commands SET last_run_at = ?, next_run_at = ? WHERE ROWID = ? END_UPDATE_COMMAND rescue Amalgalite::SQLite3::Error => error # failed to update command # do nothing: command will be run again log.error( "Database bad command (#{command[:code]}) update " + "error: #{error.}." ) end end rescue Amalgalite::SQLite3::Error => error # failed to get a write lock # try again to update commands later log.error("Database complete command locking error: #{error.}.") end |
#current_commands ⇒ Object
Returns all current commands (id
, timeout
, interval
, last_run_at
, and code
) that should be executed as part of the current snapshot. An empty Array is returned if no commands are found.
111 112 113 114 115 116 117 118 119 120 121 122 |
# File 'lib/scout_agent/database/snapshots.rb', line 111 def current_commands query(<<-END_FIND_COMMANDS.trim, Time.now.to_db_s) { |row| SELECT ROWID AS id, timeout, interval, last_run_at, code FROM commands WHERE next_run_at <= ? END_FIND_COMMANDS row[:last_run_at] = Time.from_db_s(row[:last_run_at]) } rescue Amalgalite::SQLite3::Error => error # failed to find commands log.error("Database commands error: #{error.}.") Array.new # return empty results end |
#current_runs ⇒ Object
This method returns command runs intended for the Scout server.
The process is very similar to how mission generated reports are pulled. See ScoutAgent::Database::MissionLog#current_reports() for details.
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 |
# File 'lib/scout_agent/database/snapshots.rb', line 209 def current_runs write_to_sqlite { |sqlite| # read the current runs begin run_ids = Array.new runs = query(<<-END_FIND_RUNS.trim) { |row| SELECT ROWID AS id, code, output, exit_status, snapshot_at AS created_at, run_time FROM runs ORDER BY snapshot_at LIMIT 500 END_FIND_RUNS if created = Time.from_db_s(row[:created_at]) row[:created_at] = created.utc.to_db_s else log.warn("Run timestamp missing.") end run_ids << row.delete_at(:id) } rescue Amalgalite::SQLite3::Error => error # failed to find runs log.error("Database runs error: #{error.}.") return Array.new # return empty results end return runs if runs.empty? # delete the runs we read begin sqlite.execute(<<-END_DELETE_RUNS.trim, *run_ids) DELETE FROM runs WHERE ROWID IN (#{(['?'] * run_ids.size).join(', ')}) END_DELETE_RUNS rescue Amalgalite::SQLite3::Error => error # failed to remove runs # cancel sending this batch log.error("Database delivered runs error: #{error.}.") sqlite.rollback # we can't submit unless we're sure they are gone return Array.new # return empty results end runs # the runs ready for sending } rescue Amalgalite::SQLite3::Error => error # failed to get a write lock # try again to read runs later log.error("Database runs locking error: #{error.}.") end |
#have_commands? ⇒ Boolean
Returns true
or false
to indicate if any commands are stored in the snapshot database. If the database is inaccessible and the answer cannot be determined, nil
is returned.
129 130 131 132 133 134 135 136 |
# File 'lib/scout_agent/database/snapshots.rb', line 129 def have_commands? read_from_sqlite { |sqlite| !!sqlite.first_value_from("SELECT ROWID FROM commands LIMIT 1") } rescue Amalgalite::SQLite3::Error => error # failed to find commands log.error("Database command check error: #{error.}.") nil # commands not found end |
#reset_all_commands ⇒ Object
All commands are reset so they will be run again at the first available opportunity.
142 143 144 145 146 147 148 149 150 151 152 |
# File 'lib/scout_agent/database/snapshots.rb', line 142 def reset_all_commands write_to_sqlite do |sqlite| sqlite.execute(<<-END_RESET_COMMANDS.trim) UPDATE commands SET next_run_at = strftime('%Y-%m-%d %H:%M', 'now', 'localtime') END_RESET_COMMANDS end rescue Amalgalite::SQLite3::Error => error # failed to reset commands # do nothing: commands will be run at their scheduled time log.error("Database command reset error: #{error.}.") end |
#update_commands(commands) ⇒ Object
Updates the list of snapshot commands
in the database. Existing commands are updated, new commands are added, and commands no longer in the list are deleted.
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 |
# File 'lib/scout_agent/database/snapshots.rb', line 63 def update_commands(commands) write_to_sqlite do |sqlite| codes = commands.map { |m| m["code"] } begin sqlite.execute(<<-END_DELETE_COMMANDS.trim, *codes) DELETE FROM commands WHERE code NOT IN (#{(['?'] * codes.size).join(', ')}) END_DELETE_COMMANDS rescue Amalgalite::SQLite3::Error => error # failed to remove log.error("Database command updating error: #{error.}.") return # try again to update commands later end commands.each do |command| params = [ command["timeout"].to_s =~ /\A\d*[1-9]\z/ ? command["timeout"].to_i : DEFAULT_TIMEOUT, (command["interval"] || DEFAULT_INTERVAL).to_i, command["code"] ] begin if sqlite.first_value_from( "SELECT ROWID FROM commands WHERE code = ? LIMIT 1", command["code"] ) sqlite.execute(<<-END_UPDATE_COMMAND.trim, *params) UPDATE commands SET timeout = ?, interval = ? WHERE code = ? END_UPDATE_COMMAND else sqlite.execute(<<-END_INSERT_COMMAND.trim, *params) INSERT INTO commands(timeout, interval, code) VALUES(?, ?, ?) END_INSERT_COMMAND end rescue Amalgalite::SQLite3::Error => error # failed to set command # do nothing: skip bad command and move on log.error( "Database bad command (#{command['code']}) error: " + "#{error.}." ) end end end rescue Amalgalite::SQLite3::Error => error # failed to get a write lock # try again to update commands later log.error("Database command update locking error: #{error.}.") end |
#update_schema(version = schema_version) ⇒ Object
Builds a schema for tables holding commands and the runs of those commands. The runs table is size controlled via a trigger to prevent infinite data growth.
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
# File 'lib/scout_agent/database/snapshots.rb', line 28 def update_schema(version = schema_version) case version when 0 <<-END_INITIAL_SCHEMA.trim CREATE TABLE commands ( code REQUIRED_TEXT_TYPE PRIMARY KEY, timeout POSITIVE_INTEGER_TYPE DEFAULT #{DEFAULT_TIMEOUT}, interval DEFAULT_INTEGER_TYPE #{DEFAULT_INTERVAL}, last_run_at DATETIME_TYPE, next_run_at DATETIME_TYPE ); DEFAULT_LOCALTIME_TRIGGER commands next_run_at trim_seconds CREATE TABLE runs ( code REQUIRED_TEXT_TYPE, output TEXT, exit_status INTEGER, snapshot_at REQUIRED_DATETIME_TYPE, run_time ZERO_OR_POSITIVE_REAL_TYPE, PRIMARY KEY(code, snapshot_at) ); LIMIT_TABLE_SIZE_TRIGGER runs #{RUNS_LIMIT} END_INITIAL_SCHEMA end end |