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(" INSERT INTO\n runs( code, output, exit_status, snapshot_at, run_time )\n VALUES( ?, ?, ?, ?, ? )\n END_INSERT_RUN\n rescue Amalgalite::SQLite3::Error => error # failed to add run\n # do nothing: skip bad command and move on\n log.error( \"Database bad command run (\#{command[:code]}) error: \" +\n \"\#{error.message}.\" )\n end\n # update command\n run_time = Time.now\n params = [ run_time.to_db_s,\n ( run_time +\n command[:interval] * 60 ).to_db_s(:trim_seconds),\n command[:id] ]\n begin\n sqlite.execute(<<-END_UPDATE_COMMAND.trim, *params)\n UPDATE commands SET last_run_at = ?, next_run_at = ? WHERE ROWID = ?\n END_UPDATE_COMMAND\n rescue Amalgalite::SQLite3::Error => error # failed to update command\n # do nothing: command will be run again\n log.error( \"Database bad command (\#{command[:code]}) update \" +\n \"error: \#{error.message}.\" )\n end\n end\nrescue Amalgalite::SQLite3::Error => error # failed to get a write lock\n # try again to update commands later\n log.error(\"Database complete command locking error: \#{error.message}.\")\nend\n".trim, *params) |
#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(" SELECT ROWID AS id, timeout, interval, last_run_at, code\n FROM commands\n WHERE next_run_at <= ?\n END_FIND_COMMANDS\n row[:last_run_at] = Time.from_db_s(row[:last_run_at])\n }\nrescue Amalgalite::SQLite3::Error => error # failed to find commands\n log.error(\"Database commands error: \#{error.message}.\")\n Array.new # return empty results\nend\n".trim, Time.now.to_db_s) { |row| |
#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(" SELECT ROWID AS id, code, output, exit_status,\n snapshot_at AS created_at, run_time\n FROM runs\n ORDER BY snapshot_at\n LIMIT 500\n END_FIND_RUNS\n if created = Time.from_db_s(row[:created_at])\n row[:created_at] = created.utc.to_db_s\n else\n log.warn(\"Run timestamp missing.\")\n end\n run_ids << row.delete_at(:id)\n }\n rescue Amalgalite::SQLite3::Error => error # failed to find runs\n log.error(\"Database runs error: \#{error.message}.\")\n return Array.new # return empty results\n end\n return runs if runs.empty?\n # delete the runs we read\n begin\n sqlite.execute(<<-END_DELETE_RUNS.trim, *run_ids)\n DELETE FROM runs\n WHERE ROWID IN (\#{(['?'] * run_ids.size).join(', ')})\n END_DELETE_RUNS\n rescue Amalgalite::SQLite3::Error => error # failed to remove runs\n # cancel sending this batch\n log.error(\"Database delivered runs error: \#{error.message}.\")\n sqlite.rollback # we can't submit unless we're sure they are gone\n return Array.new # return empty results\n end\n runs # the runs ready for sending\n }\nrescue Amalgalite::SQLite3::Error => error # failed to get a write lock\n # try again to read runs later\n log.error(\"Database runs locking error: \#{error.message}.\")\nend\n".trim) { |row| |
#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.message}.") 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(" UPDATE commands\n SET next_run_at = strftime('%Y-%m-%d %H:%M', 'now', 'localtime')\n END_RESET_COMMANDS\n end\nrescue Amalgalite::SQLite3::Error => error # failed to reset commands\n # do nothing: commands will be run at their scheduled time\n log.error(\"Database command reset error: \#{error.message}.\")\nend\n".trim) |
#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(" DELETE FROM commands\n WHERE code NOT IN (\#{(['?'] * codes.size).join(', ')})\n END_DELETE_COMMANDS\n rescue Amalgalite::SQLite3::Error => error # failed to remove\n log.error(\"Database command updating error: \#{error.message}.\")\n return # try again to update commands later\n end\n commands.each do |command|\n params = [ command[\"timeout\"].to_s =~ /\\A\\d*[1-9]\\z/ ?\n command[\"timeout\"].to_i :\n DEFAULT_TIMEOUT,\n (command[\"interval\"] || DEFAULT_INTERVAL).to_i,\n command[\"code\"] ]\n begin\n if sqlite.first_value_from(\n \"SELECT ROWID FROM commands WHERE code = ? LIMIT 1\",\n command[\"code\"]\n )\n sqlite.execute(<<-END_UPDATE_COMMAND.trim, *params)\n UPDATE commands SET timeout = ?, interval = ? WHERE code = ?\n END_UPDATE_COMMAND\n else\n sqlite.execute(<<-END_INSERT_COMMAND.trim, *params)\n INSERT INTO commands(timeout, interval, code) VALUES(?, ?, ?)\n END_INSERT_COMMAND\n end\n rescue Amalgalite::SQLite3::Error => error # failed to set command\n # do nothing: skip bad command and move on\n log.error( \"Database bad command (\#{command['code']}) error: \" +\n \"\#{error.message}.\" )\n end\n end\n end\nrescue Amalgalite::SQLite3::Error => error # failed to get a write lock\n # try again to update commands later\n log.error(\"Database command update locking error: \#{error.message}.\")\nend\n".trim, *codes) |
#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 " CREATE TABLE commands (\n code REQUIRED_TEXT_TYPE PRIMARY KEY,\n timeout POSITIVE_INTEGER_TYPE DEFAULT \#{DEFAULT_TIMEOUT},\n interval DEFAULT_INTEGER_TYPE \#{DEFAULT_INTERVAL},\n last_run_at DATETIME_TYPE,\n next_run_at DATETIME_TYPE\n );\n DEFAULT_LOCALTIME_TRIGGER commands next_run_at trim_seconds\n\n CREATE TABLE runs (\n code REQUIRED_TEXT_TYPE,\n output TEXT,\n exit_status INTEGER,\n snapshot_at REQUIRED_DATETIME_TYPE,\n run_time ZERO_OR_POSITIVE_REAL_TYPE,\n PRIMARY KEY(code, snapshot_at)\n );\n LIMIT_TABLE_SIZE_TRIGGER runs \#{RUNS_LIMIT}\n END_INITIAL_SCHEMA\n end\nend\n".trim |