Compare commits

..
17 changed files with 385 additions and 535 deletions

No files matched your search

+16
View File
@@ -17,6 +17,7 @@ import time
EXPECTED_CONTAINERS_FILE = "/opt/so/conf/so-status/so-status.conf"
HIGHSTATE_COMPLETE_FILE = "/opt/so/log/salt/lasthighstate"
UPTIME_FILE = "/proc/uptime"
POSTGRES_MAINTENANCE_STATUS_FILE = "/opt/so/log/postgres/maintenance.status"
def showUsage(options, args):
print('Usage: {} [OPTIONS]'.format(sys.argv[0]))
@@ -149,6 +150,20 @@ def check_container_status(options, console):
return code, container_list
def check_postgres_maintenance(code, container_list):
try:
with open(POSTGRES_MAINTENANCE_STATUS_FILE, "r") as status_file:
status = status_file.readline().strip()
except OSError:
return code
if status.startswith("failed"):
reason = status[len("failed"):].lstrip(":").strip()
container_list.append({ "Name": "so-postgres-maintenance", "Status": "failed", "Details": reason })
if code == 0:
code = 1
return code
def check_status(options, console):
container_list = []
code = check_installation_status(options, console)
@@ -160,6 +175,7 @@ def check_status(options, console):
# on the Grid until a highstate completes.
if code == 0 or code == 2:
container_code, container_list = check_container_status(options, console)
container_code = check_postgres_maintenance(container_code, container_list)
if code == 0:
code = container_code
output(options, console, code, container_list)
+9 -23
View File
@@ -17,9 +17,7 @@ INSTALLEDVERSION=$(cat /etc/soversion)
# not finish its post-upgrade steps; its contents are the resume point. It is read
# here before preupgrade_changes mutates INSTALLEDVERSION and before any highstate
# stamps /etc/soversion from the pillar.
POSTVERSION_FILE=/etc/sopostversion
if [ -f "$POSTVERSION_FILE" ]; then
POSTVERSION=$(cat "$POSTVERSION_FILE")
else
@@ -1068,31 +1066,20 @@ telegraf_repair() {
# when there is something to repair, so everyone else is left alone.
local repair=/usr/sbin/so-telegraf-repair
[[ -x "$repair" ]] || return 0
local state
state=$(docker inspect -f '{{.State.Running}} {{.State.Restarting}}' so-postgres 2>/dev/null) || return 0
if [[ "$state" != "true false" ]]; then
echo " so-postgres is not running; skipping the Telegraf partition check."
FINAL_MESSAGE_QUEUE+=("WARNING: so-postgres is not running, so Telegraf partitions were not checked. If /nsm is full, follow \"Telegraf Partition Repair\" at:")
FINAL_MESSAGE_QUEUE+=(" - https://securityonion.net/docs/postgresql")
return 0
fi
docker ps --format '{{.Names}}' | grep -qx so-postgres || return 0
echo "Checking Telegraf metric partitions."
local out status=0
out=$("$repair" --check 2>&1) || status=$?
echo "$out" >> "$SOUP_LOG"
local status=0
local max_age
max_age=$(lookup_pillar maintenance_max_age_hours postgres:telegraf 2>/dev/null) || true
max_age=${max_age:-3}
MAINTENANCE_MAX_AGE_HOURS="$max_age" "$repair" --check >> "$SOUP_LOG" 2>&1 || status=$?
case "$status" in
0) echo " Telegraf partitions are healthy; nothing to repair." ;;
1) echo " Repairing stalled Telegraf partitions."
if ! "$repair" --yes; then
echo " warning: so-telegraf-repair failed; run it manually" >&2
FINAL_MESSAGE_QUEUE+=("WARNING: Telegraf partition repair failed; Postgres metrics will keep filling /nsm until it is repaired:")
FINAL_MESSAGE_QUEUE+=(" - sudo so-telegraf-repair")
fi ;;
2) echo " Skipping: $(tail -n 1 <<<"$out")" ;;
*) echo " warning: could not check Telegraf partitions" >&2
FINAL_MESSAGE_QUEUE+=("WARNING: Telegraf partitions could not be checked during the upgrade; run:")
FINAL_MESSAGE_QUEUE+=(" - sudo so-telegraf-repair --check") ;;
MAINTENANCE_MAX_AGE_HOURS="$max_age" "$repair" --yes \
|| echo " warning: so-telegraf-repair failed; run it manually" >&2 ;;
*) echo " Skipping; Telegraf is not storing metrics in Postgres on this host." ;;
esac
}
@@ -1220,7 +1207,6 @@ add_detection_runtime_fields() {
### 3.4.0 End ###
### 3.5.0 Scripts ###
post_to_3.5.0() {
telegraf_repair
+2 -2
View File
@@ -54,9 +54,9 @@ so-nginx:
- /opt/so/log/nginx/:/var/log/nginx:rw
- /opt/so/tmp/nginx/:/var/lib/nginx:rw
- /opt/so/tmp/nginx/:/run:rw
- /nsm/elastic-fleet/artifacts/:/opt/socore/elastic-artifacts/artifacts:ro
{% if GLOBALS.is_manager %}
- /nsm/elastic-fleet/so_agent-installers/:/opt/socore/html/packages
- /nsm/elastic-fleet/artifacts/:/opt/socore/html/artifacts
{% if GLOBALS.is_manager %}
- /etc/pki/managerssl.crt:/etc/pki/nginx/server.crt:ro
- /etc/pki/managerssl.key:/etc/pki/nginx/server.key:ro
# ATT&CK Navigator binds
+34 -15
View File
@@ -4,8 +4,6 @@
{%- set role = grains.id.split('_') | last %}
{%- set influxpass = salt['pillar.get']('secrets:influx_pass') %}
{%- set influxauth = ('so:' + influxpass) | base64_encode %}
{%- set all_names = [GLOBALS.hostname, GLOBALS.url_base] + NGINXMERGED.ssl.alt_names %}
{%- set full_server_name = all_names | unique | join(' ') %}
worker_processes auto;
error_log /var/log/nginx/error.log;
@@ -41,31 +39,52 @@ http {
include /etc/nginx/conf.d/*.conf;
{%- if role in ['fleet'] %}
server {
listen 8443;
server_name {{ GLOBALS.hostname }};
root /opt/socore/html;
location /artifacts/ {
try_files $uri =206;
proxy_read_timeout 90;
proxy_connect_timeout 90;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Proxy "";
proxy_set_header X-Forwarded-Proto $scheme;
}
}
{%- endif %}
{%- if GLOBALS.is_manager %}
{%- set all_names = [GLOBALS.hostname, GLOBALS.url_base] + NGINXMERGED.ssl.alt_names %}
{%- set full_server_name = all_names | unique | join(' ') %}
server {
listen 80 default_server;
server_name _;
return 307 https://{{ GLOBALS.url_base }}$request_uri;
}
{%- endif %}
{%- if GLOBALS.is_manager or role == 'fleet' %}
server {
listen 8443;
{%- if GLOBALS.is_manager %}
server_name {{ full_server_name }};
{%- else %}
server_name {{ GLOBALS.hostname }};
{%- endif %}
root /opt/socore/elastic-artifacts;
root /opt/socore/html;
location /artifacts/ {
try_files $uri =404;
}
location / {
return 404;
try_files $uri =206;
proxy_read_timeout 90;
proxy_connect_timeout 90;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Proxy "";
proxy_set_header X-Forwarded-Proto $scheme;
}
}
{%- endif %}
{%- if GLOBALS.is_manager %}
server {
listen 443 ssl default_server;
http2 on;
+3
View File
@@ -2,6 +2,7 @@ postgres:
enabled: True
telegraf:
retention_days: 14
maintenance_max_age_hours: 3
config:
listen_addresses: '*'
port: 5432
@@ -17,4 +18,6 @@ postgres:
log_min_messages: 'warning'
shared_preload_libraries: pg_cron
cron.database_name: postgres
# pg_hba.conf only trusts local socket connections; over TCP pg_cron has no
# password to send and every job fails to connect.
cron.host: /run/postgresql
+9
View File
@@ -24,6 +24,15 @@ so_postgres_backup:
- identifier: so_postgres_backup
- user: root
so_postgres_maintenance_status:
cron.absent:
- identifier: so_postgres_maintenance_status
- user: root
so_postgres_maintenance_status_file:
file.absent:
- name: /opt/so/log/postgres/maintenance.status
{% else %}
{{sls}}_state_not_allowed:
+24
View File
@@ -8,6 +8,7 @@
{% from 'vars/globals.map.jinja' import GLOBALS %}
{% from 'ca/trust.map.jinja' import CATRUST %}
{% from 'docker/docker.map.jinja' import DOCKERMERGED %}
{% from 'telegraf/map.jinja' import TELEGRAFMERGED %}
{% set SO_POSTGRES_USER = salt['pillar.get']('postgres:auth:users:so_postgres_user:user', 'so_postgres') %}
include:
@@ -122,6 +123,29 @@ so_postgres_backup:
- month: '*'
- dayweek: '*'
{% if TELEGRAFMERGED.output | upper in ['POSTGRES', 'BOTH'] %}
{% set max_age = salt['pillar.get']('postgres:telegraf:maintenance_max_age_hours', 3) | int %}
so_postgres_maintenance_status:
cron.present:
- name: MAINTENANCE_MAX_AGE_HOURS={{ max_age }} /usr/sbin/so-telegraf-postgres status > /dev/null 2>&1
- identifier: so_postgres_maintenance_status
- user: root
- minute: '*/15'
- hour: '*'
- daymonth: '*'
- month: '*'
- dayweek: '*'
{% else %}
so_postgres_maintenance_status:
cron.absent:
- identifier: so_postgres_maintenance_status
- user: root
so_postgres_maintenance_status_file:
file.absent:
- name: /opt/so/log/postgres/maintenance.status
{% endif %}
{% else %}
{{sls}}_state_not_allowed:
+6 -2
View File
@@ -9,6 +9,11 @@ postgres:
description: Number of days of Telegraf metrics to keep in the so_telegraf database. Older partitions are dropped hourly by pg_partman.
forcedType: int
helpLink: postgres
maintenance_max_age_hours:
description: Number of hours without a successful run of the hourly pg_partman maintenance job before the Telegraf metrics database is reported as unhealthy in grid status and repaired by soup.
forcedType: int
advanced: True
helpLink: postgres
config:
max_connections:
description: Maximum number of concurrent PostgreSQL connections.
@@ -88,8 +93,7 @@ postgres:
advanced: True
helpLink: postgres
cron.host:
description: Where pg_cron connects to run scheduled jobs such as Telegraf partition maintenance. Must be the container's PostgreSQL socket directory, /run/postgresql, because pg_cron connects without a password and only local socket connections are trusted. Any other value makes every scheduled job fail. Changing it restarts PostgreSQL.
readonly: True
description: Unix socket directory pg_cron uses to run scheduled jobs. Must be a local socket directory so jobs authenticate with the local trust rule; a TCP host requires a password and jobs will fail.
global: True
advanced: True
helpLink: postgres
+1 -10
View File
@@ -52,7 +52,7 @@ if [ -n "$FILTER_HOST" ] && ! [[ "$FILTER_HOST" =~ $HOST_RE ]]; then
fi
so_psql() {
docker exec -e PGOPTIONS='-c application_name=so-stats-show' so-postgres psql -U so_maint_telegraf -d so_telegraf -At -F $'\t' "$@"
docker exec so-postgres psql -U postgres -d so_telegraf -At -F $'\t' "$@"
}
if ! docker exec so-postgres psql -U postgres -lqt 2>/dev/null | cut -d\| -f1 | grep -qw so_telegraf; then
@@ -60,15 +60,6 @@ if ! docker exec so-postgres psql -U postgres -lqt 2>/dev/null | cut -d\| -f1 |
exit 2
fi
role_ok=$(docker exec so-postgres psql -U postgres -tAc "SELECT rolcanlogin AND NOT (rolsuper OR rolcreatedb OR rolcreaterole OR rolreplication OR rolbypassrls) FROM pg_roles WHERE rolname = 'so_maint_telegraf'" 2>/dev/null)
if [ -z "$role_ok" ]; then
echo "The so_maint_telegraf role is missing; run a highstate."
exit 1
elif [ "$role_ok" != t ]; then
echo "The so_maint_telegraf role is misconfigured; run a highstate."
exit 1
fi
table_exists() {
local table="$1"
[ -n "$(so_psql -c "SELECT 1 FROM information_schema.tables WHERE table_schema='${SCHEMA}' AND table_name='${table}' LIMIT 1;")" ]
+143 -196
View File
@@ -4,18 +4,19 @@ set -e
# Provision Telegraf state inside the so-postgres container.
# Usage: so-telegraf-postgres <subcommand>
# create_db Ensure the so_telegraf database exists.
# group_role Provision the so_telegraf group role, the so_maint_telegraf
# maintenance login role, telegraf/partman schemas, pg_partman,
# the so_admin maintenance routines, and the hourly pg_cron
# maintenance job.
# group_role Provision the so_telegraf group role, telegraf/partman schemas,
# pg_partman, the so_admin maintenance routines, and the hourly
# pg_cron maintenance job.
# user Create or update a per-minion login role granted to so_telegraf.
# Env: ROLE_USER, ROLE_PASS.
# retention Reconcile partman retention and premake on telegraf parents.
# Env: RETENTION_DAYS.
# maintenance Drain default partitions and run partman maintenance, as
# so_maint_telegraf.
# check Report partition health, as so_maint_telegraf. Non-zero if any
# parent is unhealthy.
# maintenance Drain default partitions and run partman maintenance.
# check Report partition and maintenance health. Non-zero if any parent
# is unhealthy or the maintenance job has no successful run within
# the last MAINTENANCE_MAX_AGE_HOURS (default 3).
# status Run check and write `ok` or `failed: <reason>` to
# /opt/so/log/postgres/maintenance.status for so-status.
#
# A default partition holding rows for a day blocks creating that day's child,
# so maintenance drains defaults before calling partman. Use so-telegraf-repair
@@ -23,10 +24,6 @@ set -e
cmd="${1:?subcommand required}"
# Long enough for an hourly maintenance run to release its locks, short enough
# that soup and admins are not left hanging.
LOCK_TIMEOUT=60s
case "$cmd" in
create_db)
if ! docker exec so-postgres psql -U postgres -tAc \
@@ -37,9 +34,7 @@ case "$cmd" in
;;
group_role)
docker exec -i so-postgres psql -v ON_ERROR_STOP=1 -v lock_timeout="$LOCK_TIMEOUT" \
-U postgres -d so_telegraf <<'EOSQL'
SET lock_timeout = :'lock_timeout';
docker exec -i so-postgres psql -v ON_ERROR_STOP=1 -U postgres -d so_telegraf <<'EOSQL'
DO $$
BEGIN
IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = 'so_telegraf') THEN
@@ -47,17 +42,6 @@ BEGIN
END IF;
END
$$;
DO $$
BEGIN
IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = 'so_maint_telegraf') THEN
CREATE ROLE so_maint_telegraf;
END IF;
END
$$;
ALTER ROLE so_maint_telegraf LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION NOBYPASSRLS PASSWORD NULL;
GRANT so_telegraf TO so_maint_telegraf;
ALTER ROLE so_maint_telegraf SET role = so_telegraf;
ALTER ROLE so_maint_telegraf SET search_path = pg_catalog;
GRANT CONNECT ON DATABASE so_telegraf TO so_telegraf;
CREATE SCHEMA IF NOT EXISTS telegraf AUTHORIZATION so_telegraf;
GRANT USAGE, CREATE ON SCHEMA telegraf TO so_telegraf;
@@ -78,90 +62,52 @@ ALTER DEFAULT PRIVILEGES IN SCHEMA partman
ALTER DEFAULT PRIVILEGES IN SCHEMA partman
GRANT USAGE, SELECT, UPDATE ON SEQUENCES TO so_telegraf;
-- Owned by postgres so no Telegraf role can create objects here; the routines
-- run with the caller's privileges.
-- pg_cron runs these as postgres, so they must not sit in a schema any
-- Telegraf role can create objects in.
CREATE SCHEMA IF NOT EXISTS so_admin AUTHORIZATION postgres;
REVOKE ALL ON SCHEMA so_admin FROM PUBLIC;
-- Resolved through pg_inherits, never by name, so a view or a table that only
-- carries the _default name is never read or written.
CREATE OR REPLACE FUNCTION so_admin.telegraf_defaults()
RETURNS TABLE (
parent_table regclass,
default_part regclass,
has_rows boolean,
default_size bigint
)
LANGUAGE plpgsql
STABLE
SECURITY INVOKER
AS $func$
DECLARE
r record;
BEGIN
FOR r IN
SELECT c.oid::regclass AS pt
FROM partman.part_config pc
JOIN pg_catalog.pg_class c ON c.oid = to_regclass(pc.parent_table)
WHERE pc.parent_table LIKE 'telegraf.%'
AND c.relkind = 'p'
ORDER BY pc.parent_table
LOOP
parent_table := r.pt;
SELECT c.oid::regclass INTO default_part
FROM pg_catalog.pg_inherits i
JOIN pg_catalog.pg_class c ON c.oid = i.inhrelid
WHERE i.inhparent = r.pt
AND c.relkind = 'r' AND c.relispartition
AND pg_catalog.pg_get_expr(c.relpartbound, c.oid) = 'DEFAULT';
IF default_part IS NULL THEN
has_rows := false;
default_size := NULL;
ELSE
EXECUTE format('SELECT EXISTS (SELECT 1 FROM %s LIMIT 1)', default_part)
INTO has_rows;
default_size := pg_catalog.pg_total_relation_size(default_part);
END IF;
RETURN NEXT;
END LOOP;
END;
$func$;
CREATE OR REPLACE PROCEDURE so_admin.telegraf_maintenance()
LANGUAGE plpgsql
SECURITY INVOKER
-- No SET clause: it would forbid the COMMITs below, so so_maint_telegraf pins search_path instead.
AS $proc$
DECLARE
r record;
r record;
v_default text;
v_rows bigint;
BEGIN
-- No per-parent EXCEPTION handler: partition_data_proc commits internally,
-- and COMMIT is illegal while a subtransaction is active. A failing parent
-- aborts the run and the next pass retries.
FOR r IN
SELECT pc.parent_table, pc.retention, d.default_part, d.default_size
FROM so_admin.telegraf_defaults() d
JOIN partman.part_config pc ON to_regclass(pc.parent_table) = d.parent_table
WHERE d.default_part IS NOT NULL AND d.has_rows
ORDER BY pc.parent_table
SELECT parent_table, retention
FROM partman.part_config
WHERE parent_table LIKE 'telegraf.%'
ORDER BY parent_table
LOOP
RAISE WARNING 'so_admin.telegraf_maintenance: % holds rows (%), draining',
r.default_part, pg_size_pretty(r.default_size);
v_default := format('%I.%I',
split_part(r.parent_table, '.', 1),
split_part(r.parent_table, '.', 2) || '_default');
CONTINUE WHEN to_regclass(v_default) IS NULL;
EXECUTE format('SELECT count(*) FROM %s', v_default) INTO v_rows;
CONTINUE WHEN v_rows = 0;
RAISE WARNING 'so_admin.telegraf_maintenance: % rows stranded in %, draining',
v_rows, v_default;
-- Cheaper to delete expired rows than to repartition and then drop them.
IF r.retention IS NOT NULL THEN
EXECUTE format('DELETE FROM %s WHERE "time" < now() - %L::interval',
r.default_part, r.retention);
v_default, r.retention);
COMMIT;
END IF;
-- Bounded so a large backlog drains across several runs.
CALL partman.partition_data_proc(
p_parent_table := r.parent_table,
p_loop_count := 200
p_loop_count := 200,
p_source_table := v_default
);
COMMIT;
END LOOP;
@@ -170,75 +116,58 @@ BEGIN
END;
$proc$;
DROP FUNCTION IF EXISTS so_admin.telegraf_partition_status();
CREATE FUNCTION so_admin.telegraf_partition_status()
CREATE OR REPLACE FUNCTION so_admin.telegraf_partition_status()
RETURNS TABLE (
parent_table text,
oldest_child date,
newest_child date,
days_ahead int,
retention text,
default_has_rows boolean,
default_size text
parent_table text,
oldest_child date,
newest_child date,
days_ahead int,
retention text,
default_rows bigint,
default_size text
)
LANGUAGE plpgsql
STABLE
AS $func$
DECLARE
r record;
r record;
v_default regclass;
BEGIN
FOR r IN
SELECT d.parent_table AS pt, d.has_rows, d.default_size AS size, pc.retention AS ret
FROM so_admin.telegraf_defaults() d
JOIN partman.part_config pc ON to_regclass(pc.parent_table) = d.parent_table
ORDER BY d.parent_table::text
SELECT pc.parent_table AS pt, pc.retention AS ret
FROM partman.part_config pc
WHERE pc.parent_table LIKE 'telegraf.%'
ORDER BY pc.parent_table
LOOP
parent_table := r.pt::text;
parent_table := r.pt;
retention := r.ret;
SELECT min(x), max(x) INTO oldest_child, newest_child
SELECT min(d), max(d) INTO oldest_child, newest_child
FROM (
SELECT to_date(substring(c.relname FROM '_p(\d{8})$'), 'YYYYMMDD') AS x
FROM pg_catalog.pg_inherits i
JOIN pg_catalog.pg_class c ON c.oid = i.inhrelid
WHERE i.inhparent = r.pt
AND pg_catalog.pg_get_expr(c.relpartbound, c.oid) <> 'DEFAULT'
SELECT to_date(substring(c.relname FROM '_p(\d{8})$'), 'YYYYMMDD') AS d
FROM pg_inherits i
JOIN pg_class c ON c.oid = i.inhrelid
WHERE i.inhparent = r.pt::regclass
AND pg_get_expr(c.relpartbound, c.oid) <> 'DEFAULT'
) s;
days_ahead := newest_child - current_date;
default_has_rows := r.has_rows;
default_size := pg_size_pretty(r.size);
days_ahead := newest_child - current_date;
v_default := to_regclass(format('%I.%I',
split_part(r.pt, '.', 1),
split_part(r.pt, '.', 2) || '_default'));
IF v_default IS NULL THEN
default_rows := NULL;
default_size := NULL;
ELSE
EXECUTE format('SELECT count(*) FROM %s', v_default::text) INTO default_rows;
default_size := pg_size_pretty(pg_total_relation_size(v_default));
END IF;
RETURN NEXT;
END LOOP;
END;
$func$;
GRANT USAGE ON SCHEMA so_admin TO so_telegraf;
GRANT EXECUTE ON FUNCTION so_admin.telegraf_defaults() TO so_telegraf;
GRANT EXECUTE ON PROCEDURE so_admin.telegraf_maintenance() TO so_telegraf;
GRANT EXECUTE ON FUNCTION so_admin.telegraf_partition_status() TO so_telegraf;
-- Partitions created by a superuser session (an older repair) belong to
-- postgres; partman acting as so_telegraf could not drop them.
DO $$
DECLARE
r record;
BEGIN
FOR r IN
SELECT n.nspname, c.relname
FROM pg_catalog.pg_class c
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind IN ('r', 'p')
AND (n.nspname = 'telegraf'
OR (n.nspname = 'partman' AND c.relname LIKE 'template\_telegraf\_%'))
AND c.relowner <> 'so_telegraf'::regrole
LOOP
EXECUTE format('ALTER TABLE %I.%I OWNER TO so_telegraf', r.nspname, r.relname);
END LOOP;
END
$$;
-- Drop the registration older releases left in so_telegraf.
SELECT CASE
WHEN current_setting('cron.database_name', true) IS DISTINCT FROM current_database()
@@ -250,69 +179,25 @@ DROP EXTENSION pg_cron CASCADE;
\endif
EOSQL
stalled=false
probe_failed=false
if out=$(docker exec -e PGOPTIONS="-c lock_timeout=$LOCK_TIMEOUT" so-postgres \
psql -U so_maint_telegraf -d so_telegraf -tAc \
"SELECT coalesce(bool_or(has_rows), false) FROM so_admin.telegraf_defaults()"); then
case "$out" in
t) stalled=true ;;
f) stalled=false ;;
*) probe_failed=true ;;
esac
else
probe_failed=true
fi
if $probe_failed; then
echo "ERROR: could not check Telegraf default partitions; the maintenance job was not changed." >&2
exit 1
fi
# Guarded on the live GUC so applying this before the postgresql.conf change
# has restarted the container skips rather than failing.
docker exec -i so-postgres psql -v ON_ERROR_STOP=1 -v stalled="$stalled" \
-U postgres -d postgres <<'EOSQL'
docker exec -i so-postgres psql -v ON_ERROR_STOP=1 -U postgres -d postgres <<'EOSQL'
SELECT CASE WHEN current_setting('cron.database_name', true) = current_database()
THEN 'true' ELSE 'false' END AS cron_here \gset
\if :cron_here
CREATE EXTENSION IF NOT EXISTS pg_cron;
-- A highstate reconciles the job's definition but never its active flag; only so-telegraf-repair turns it on.
BEGIN;
SELECT coalesce(max(jobid)::text, '') AS jobid,
count(*) > 0 AS job_exists,
coalesce(bool_and(active), true) AS job_active
FROM cron.job
WHERE jobname = 'telegraf-partman-maintenance' AND username = 'so_maint_telegraf' \gset
SELECT cron.unschedule(jobid) FROM cron.job
WHERE jobname = 'telegraf-partman-maintenance' AND username <> 'so_maint_telegraf';
\if :job_exists
SELECT cron.alter_job(:jobid,
schedule := '17 * * * *',
command := 'CALL so_admin.telegraf_maintenance()',
database := 'so_telegraf');
\else
-- Registered inactive on a stalled grid so nothing drains the default
-- partitions before so-telegraf-repair truncates them.
-- cron.schedule_in_database is idempotent by jobname.
SELECT cron.schedule_in_database(
'telegraf-partman-maintenance',
'17 * * * *',
'CALL so_admin.telegraf_maintenance()',
'so_telegraf',
'so_maint_telegraf',
NOT :'stalled'::boolean
'so_telegraf'
);
\endif
-- pg_cron copies cron.host into nodename only when a job is first created, so
-- jobs registered before cron.host changed keep connecting to the old host.
UPDATE cron.job SET nodename = current_setting('cron.host')
WHERE jobname = 'telegraf-partman-maintenance'
AND nodename IS DISTINCT FROM current_setting('cron.host');
COMMIT;
SELECT coalesce(bool_and(active), true) AS job_active
FROM cron.job
WHERE jobname = 'telegraf-partman-maintenance' AND username = 'so_maint_telegraf' \gset
\if :job_active
\else
\echo 'WARNING: the Telegraf maintenance job is inactive; run ''sudo so-telegraf-repair''.'
\endif
WHERE jobname = 'telegraf-partman-maintenance'
AND nodename IS DISTINCT FROM current_setting('cron.host');
\else
\echo 'pg_cron metadata database is not `postgres` yet; skipping job registration.'
\endif
@@ -365,15 +250,13 @@ SET retention = :'retention_days' || ' days',
retention_keep_table = false,
premake = 7,
infinite_time_partitions = true
WHERE parent_table LIKE 'telegraf.%'
AND EXISTS (SELECT 1 FROM pg_catalog.pg_class c
WHERE c.oid = to_regclass(parent_table) AND c.relkind = 'p');
WHERE parent_table LIKE 'telegraf.%';
\endif
EOSQL
;;
maintenance)
docker exec -i so-postgres psql -v ON_ERROR_STOP=1 -U so_maint_telegraf -d so_telegraf <<'EOSQL'
docker exec -i so-postgres psql -v ON_ERROR_STOP=1 -U postgres -d so_telegraf <<'EOSQL'
SELECT CASE WHEN to_regproc('so_admin.telegraf_maintenance') IS NOT NULL
THEN 'true' ELSE 'false' END AS has_proc \gset
\if :has_proc
@@ -385,7 +268,7 @@ EOSQL
;;
check)
docker exec -i so-postgres psql -U so_maint_telegraf -d so_telegraf <<'EOSQL'
docker exec -i so-postgres psql -U postgres -d so_telegraf <<'EOSQL'
\pset border 2
SELECT * FROM so_admin.telegraf_partition_status();
EOSQL
@@ -404,16 +287,80 @@ LIMIT 5;
\echo 'pg_cron is not installed in this database.'
\endif
EOSQL
unhealthy=$(docker exec so-postgres psql -U so_maint_telegraf -d so_telegraf -tAc \
max_age="${MAINTENANCE_MAX_AGE_HOURS:-3}"
[[ "$max_age" =~ ^[0-9]+$ ]] || { echo "MAINTENANCE_MAX_AGE_HOURS must be an integer" >&2; exit 1; }
failures=()
unhealthy=$(docker exec so-postgres psql -U postgres -d so_telegraf -tAc \
"SELECT count(*) FROM so_admin.telegraf_partition_status()
WHERE default_has_rows OR coalesce(days_ahead, -1) < 1")
WHERE coalesce(default_rows, 0) > 0 OR coalesce(days_ahead, -1) < 1")
if [ "${unhealthy:-1}" != "0" ]; then
echo "so-telegraf-postgres check: $unhealthy telegraf parent(s) unhealthy" >&2
failures+=("$unhealthy telegraf parent(s) unhealthy")
fi
has_cron=$(docker exec so-postgres psql -U postgres -d postgres -tAc \
"SELECT to_regclass('cron.job_run_details') IS NOT NULL")
if [ "$has_cron" != "t" ]; then
failures+=("pg_cron is not installed in the postgres database")
else
runs=$(docker exec so-postgres psql -U postgres -d postgres -tA -F '|' -c \
"SELECT (SELECT count(*) FROM cron.job WHERE jobname = 'telegraf-partman-maintenance'),
(SELECT count(*) FROM cron.job WHERE jobname = 'telegraf-partman-maintenance' AND active),
(SELECT count(*) FROM pg_stat_activity
WHERE backend_type ILIKE '%pg_cron%' OR application_name ILIKE '%pg_cron%'),
(SELECT count(*) FROM pg_stat_activity
WHERE (backend_type ILIKE '%pg_cron%' OR application_name ILIKE '%pg_cron%')
AND backend_start < now() - make_interval(hours => $max_age)),
count(*),
count(*) FILTER (WHERE d.status = 'succeeded'
AND d.start_time > now() - make_interval(hours => $max_age)),
coalesce((array_agg(format('last run %s at %s: %s', d.status, d.start_time,
regexp_replace(coalesce(d.return_message, ''), '\s+', ' ', 'g'))
ORDER BY d.start_time DESC))[1], '')
FROM cron.job j
JOIN cron.job_run_details d ON d.jobid = j.jobid
WHERE j.jobname = 'telegraf-partman-maintenance'") || runs=""
IFS='|' read -r jobs active launchers old_launchers run_count recent_ok last_run <<< "$runs"
# With no runs recorded, a live launcher younger than N hours means the
# job was only just scheduled (fresh install or a restart for a config
# change) and has not reached its first :17 yet. A launcher older than
# that should have fired it by now. Tradeoff: a job first scheduled long
# after the launcher started reports failed until its first run.
if [ -z "$runs" ]; then
failures+=("could not read telegraf-partman-maintenance runs from cron.job_run_details")
elif [ "$jobs" = "0" ]; then
failures+=("telegraf-partman-maintenance is not scheduled")
elif [ "$active" = "0" ]; then
failures+=("telegraf-partman-maintenance is inactive")
elif [ "$launchers" = "0" ]; then
failures+=("pg_cron launcher is not running; telegraf-partman-maintenance cannot run")
elif [ "$run_count" = "0" ]; then
if [ "$old_launchers" != "0" ]; then
failures+=("telegraf-partman-maintenance has never run although pg_cron has been up for more than $max_age hour(s)")
fi
elif [ "$recent_ok" = "0" ]; then
failures+=("no successful telegraf-partman-maintenance run in the last $max_age hour(s); $last_run")
fi
fi
if [ "${#failures[@]}" -gt 0 ]; then
msg=$(printf '%s; ' "${failures[@]}")
echo "so-telegraf-postgres check: ${msg%; }" >&2
exit 1
fi
echo "so-telegraf-postgres check: all telegraf parents healthy"
;;
status)
status_file=/opt/so/log/postgres/maintenance.status
tmp=$(mktemp "$status_file.XXXXXX")
if reason=$("$0" check 2>&1 >/dev/null); then
echo ok > "$tmp"
else
reason=$(printf '%s\n' "$reason" | tail -n 1)
echo "failed: ${reason#so-telegraf-postgres check: }" > "$tmp"
fi
chmod 644 "$tmp"
mv -f "$tmp" "$status_file"
;;
*)
echo "Unknown subcommand: $cmd" >&2
exit 1
+107 -225
View File
@@ -11,38 +11,31 @@
# maintenance once. Healthy grids are reported and left alone.
#
# Usage: so-telegraf-repair [--check] [--yes] [--no-restart]
# --check Report health and change nothing. Also reports a missing
# so_maint_telegraf role and a maintenance job that is missing
# or inactive.
# --check Report health and change nothing.
# --yes Skip the confirmation prompt (for soup and other automation).
# --no-restart Never restart so-postgres, even if pg_cron's launcher is dead.
#
# Repair mode (without --check) registers the so_maint_telegraf role and the
# maintenance job if they are missing, pauses the job while it repairs, and
# always leaves it active afterwards. Data work runs as so_maint_telegraf.
# Env: MAINTENANCE_MAX_AGE_HOURS (default 3) is how long the maintenance job may
# go without a successful run before repair is needed.
#
# Exit status:
# 0 healthy, or repair completed
# 1 repair is needed (--check), or the repair failed after it started
# 2 skipped: Telegraf output is not Postgres, or so-postgres, so_telegraf or pg_partman is missing
# 3 could not check, for example a connection error
# 1 repair is needed (--check only)
# 2 cannot run here: so-postgres, so_telegraf or pg_partman is missing
set -eE
set -e
# Matches p_premake in telegraf.conf's create_parent template.
PREMAKE=7
JOB_NAME=telegraf-partman-maintenance
MAINT_ROLE=so_maint_telegraf
# Long enough for an hourly maintenance run to release its locks, short enough
# that soup and admins are not left hanging.
LOCK_TIMEOUT=60s
PG_OPTS="-c lock_timeout=$LOCK_TIMEOUT -c application_name=so-telegraf-repair"
MAX_AGE_HOURS="${MAINTENANCE_MAX_AGE_HOURS:-3}"
[[ "$MAX_AGE_HOURS" =~ ^[0-9]+$ ]] || { echo "MAINTENANCE_MAX_AGE_HOURS must be an integer" >&2; exit 2; }
CHECK_ONLY=false
ASSUME_YES=false
NO_RESTART=false
usage() { sed -n '/^# Usage:/,/^# 3 /p' "$0" | sed 's/^# \?//'; }
usage() { sed -n '/^# Usage:/,/^# 2 /p' "$0" | sed 's/^# \?//'; }
while [[ $# -gt 0 ]]; do
case "$1" in
@@ -50,170 +43,75 @@ while [[ $# -gt 0 ]]; do
--yes|-y) ASSUME_YES=true ;;
--no-restart) NO_RESTART=true ;;
-h|--help) usage; exit 0 ;;
*) echo "Unknown option: $1" >&2; usage >&2; exit 3 ;;
*) echo "Unknown option: $1" >&2; usage >&2; exit 2 ;;
esac
shift
done
# Strips C0 (except tab and newline), DEL and C1 characters, so names and
# messages written with Telegraf credentials can't drive the terminal.
sanitise() { LC_ALL=C tr -d '\000-\010\013-\037\177' | LC_ALL=C sed 's/\xc2[\x80-\x9f]//g'; }
skip() { echo "$*"; exit 2; }
ERR_FILE=$(mktemp)
ERR_STATUS=3
ERR_PREFIX="Could not check Telegraf partitions"
job_id=
restore_active=
on_err() {
# Command substitutions inherit this trap; only the top level reports.
[[ $BASH_SUBSHELL -eq 0 ]] || exit "$ERR_STATUS"
local line
line=$(tail -n 1 "$ERR_FILE" | sanitise)
echo "$ERR_PREFIX: ${line:-command failed}" >&2
exit "$ERR_STATUS"
}
on_exit() {
if [[ -n "$restore_active" ]]; then
psql_cron -tAc "SELECT cron.alter_job($job_id, active := $restore_active)" >/dev/null || true
fi
rm -f "$ERR_FILE"
}
trap on_err ERR
trap on_exit EXIT
finish() { trap - ERR; exit "$1"; }
skip() { echo "$*"; finish 2; }
# Keeps the last stderr line for on_err while still showing all of it.
capture() {
local rc=0
: >"$ERR_FILE"
"$@" 2>"$ERR_FILE" || rc=$?
sanitise <"$ERR_FILE" >&2
return "$rc"
}
psql_tg() {
capture docker exec -i -e PGOPTIONS="$PG_OPTS" so-postgres psql -U "$MAINT_ROLE" -d so_telegraf "$@"
}
psql_pg() {
capture docker exec -i -e PGOPTIONS="$PG_OPTS" so-postgres psql -U postgres -d postgres "$@"
}
psql_cron() {
capture docker exec -i -e PGOPTIONS="$PG_OPTS" so-postgres psql -U postgres -d "$cron_db" "$@"
}
# A part_config row counts only while its parent is a partitioned table.
TELEGRAF_PARENTS="parent_table LIKE 'telegraf.%'
AND EXISTS (SELECT 1 FROM pg_class c
WHERE c.oid = to_regclass(parent_table) AND c.relkind = 'p')"
psql_tg() { docker exec -i so-postgres psql -U postgres -d so_telegraf "$@"; }
psql_pg() { docker exec -i so-postgres psql -U postgres -d postgres "$@"; }
# query_to_xml so the per-table row counts need no helper function installed.
REPORT="
WITH parents AS (
SELECT pc.parent_table,
pc.premake,
split_part(pc.parent_table, '.', 1) AS sch,
split_part(pc.parent_table, '.', 2) AS tbl
FROM partman.part_config pc
WHERE pc.parent_table LIKE 'telegraf.%'
), children AS (
SELECT p.parent_table,
max(to_date(substring(c.relname FROM '_p(\d{8})\$'), 'YYYYMMDD')) AS newest_child
FROM parents p
JOIN pg_class pt ON pt.oid = p.parent_table::regclass
JOIN pg_inherits i ON i.inhparent = pt.oid
JOIN pg_class c ON c.oid = i.inhrelid
WHERE pg_get_expr(c.relpartbound, c.oid) <> 'DEFAULT'
GROUP BY p.parent_table
), defaults AS (
SELECT p.parent_table,
p.premake,
format('%I.%I', p.sch, p.tbl || '_default') AS default_table,
to_regclass(format('%I.%I', p.sch, p.tbl || '_default')) AS default_oid
FROM parents p
)
SELECT d.parent_table,
c.newest_child,
(c.newest_child - current_date) AS days_ahead,
pc.premake,
d.has_rows AS default_has_rows,
pg_size_pretty(d.default_size) AS default_size
FROM so_admin.telegraf_defaults() d
JOIN partman.part_config pc ON to_regclass(pc.parent_table) = d.parent_table
JOIN pg_class p ON p.oid = d.parent_table AND p.relkind = 'p'
LEFT JOIN LATERAL (
SELECT max(to_date(substring(ch.relname FROM '_p(\d{8})\$'), 'YYYYMMDD')) AS newest_child
FROM pg_inherits i
JOIN pg_class ch ON ch.oid = i.inhrelid
WHERE i.inhparent = d.parent_table
AND pg_get_expr(ch.relpartbound, ch.oid) <> 'DEFAULT'
) c ON true
ORDER BY d.parent_table::text
d.premake,
CASE WHEN d.default_oid IS NULL THEN NULL ELSE
(xpath('/row/cnt/text()',
query_to_xml(format('SELECT count(*) AS cnt FROM %s', d.default_table),
false, true, '')))[1]::text::bigint
END AS default_rows,
CASE WHEN d.default_oid IS NULL THEN NULL
ELSE pg_size_pretty(pg_total_relation_size(d.default_oid)) END AS default_size
FROM defaults d
LEFT JOIN children c ON c.parent_table = d.parent_table
ORDER BY 1
"
print_report() {
local report
report=$(psql_tg -c "$REPORT")
sanitise <<<"$report"
}
has_cron() {
[[ -n "$cron_db" ]] || return 0
psql_cron -tAc "SELECT 1 FROM pg_extension WHERE extname = 'pg_cron'"
}
lookup_job() {
local cron
cron=$(has_cron)
[[ -n "$cron" ]] || return 0
psql_cron -tA -F '|' -c \
"SELECT jobid, active FROM cron.job
WHERE jobname = '$JOB_NAME' AND username = '$MAINT_ROLE'
ORDER BY jobid LIMIT 1"
}
output=$(capture salt-call pillar.get telegraf:output --out=newline_values_only)
output=$(tr -d '[:space:]' <<<"$output" | tr '[:lower:]' '[:upper:]')
output=${output:-INFLUXDB}
case "$output" in
POSTGRES|BOTH) ;;
*) skip "Telegraf output is $(sanitise <<<"$output"); not checking Postgres partitions." ;;
esac
containers=$(capture docker ps --format '{{.Names}}')
grep -qx so-postgres <<<"$containers" \
docker ps --format '{{.Names}}' | grep -qx so-postgres \
|| skip "so-postgres is not running; nothing to repair."
has_db=$(psql_pg -tAc "SELECT 1 FROM pg_database WHERE datname = 'so_telegraf'")
[[ -n "$has_db" ]] \
docker exec so-postgres psql -U postgres -tAc \
"SELECT 1 FROM pg_database WHERE datname='so_telegraf'" | grep -q 1 \
|| skip "The so_telegraf database does not exist; Telegraf is not writing to Postgres."
# so_telegraf before the postgres state lands, postgres after.
cron_db=$(psql_pg -tAc "SELECT current_setting('cron.database_name', true)")
cron_db=$(tr -d '[:space:]' <<<"$cron_db")
role_ok=$(psql_pg -tAc \
"SELECT rolcanlogin AND NOT (rolsuper OR rolcreatedb OR rolcreaterole OR rolreplication OR rolbypassrls)
FROM pg_roles WHERE rolname = '$MAINT_ROLE'")
job=
[[ "$cron_db" == postgres ]] && job=$(lookup_job)
if [[ "$role_ok" != t ]] && $CHECK_ONLY; then
echo "Repair is needed:"
if [[ -z "$role_ok" ]]; then
echo " * maintenance role $MAINT_ROLE is missing"
else
echo " * maintenance role $MAINT_ROLE is misconfigured"
fi
echo
echo "Re-run without --check to repair."
finish 1
fi
if ! $CHECK_ONLY && [[ "$role_ok" != t || ( "$cron_db" == postgres && -z "$job" ) ]]; then
echo "Registering the maintenance role and job."
if ! /usr/sbin/so-telegraf-postgres group_role; then
echo "Could not register the maintenance job; run a highstate and try again." >&2
finish 3
fi
[[ "$cron_db" == postgres ]] && job=$(lookup_job)
fi
has_partman=$(psql_tg -tAc "SELECT 1 FROM pg_extension WHERE extname = 'pg_partman'")
[[ -n "$has_partman" ]] \
psql_tg -tAc "SELECT 1 FROM pg_extension WHERE extname='pg_partman'" | grep -q 1 \
|| skip "pg_partman is not installed in so_telegraf; nothing to repair."
parents=$(psql_tg -tAc \
"SELECT count(*) FROM partman.part_config WHERE $TELEGRAF_PARENTS")
counts=$(psql_tg -tAc \
"SELECT count(*) FILTER (WHERE default_has_rows) || '|' ||
count(*) FILTER (WHERE coalesce(days_ahead, -1) < 1)
FROM ( $REPORT ) t")
IFS='|' read -r stranded behind <<<"$counts"
"SELECT count(*) FROM partman.part_config WHERE parent_table LIKE 'telegraf.%'")
stranded=$(psql_tg -tAc "SELECT coalesce(sum(default_rows), 0) FROM ( $REPORT ) t")
behind=$(psql_tg -tAc \
"SELECT count(*) FROM ( $REPORT ) t WHERE coalesce(days_ahead, -1) < 1")
# premake < 7, or infinite_time_partitions off: without the latter partman
# refuses to premake forward across the gap the stall left behind.
misconfigured=$(psql_tg -tAc \
"SELECT count(*) FROM partman.part_config
WHERE $TELEGRAF_PARENTS
WHERE parent_table LIKE 'telegraf.%'
AND (premake < $PREMAKE OR NOT infinite_time_partitions)")
# Both columns are matched because which one carries the launcher's name varies
@@ -222,92 +120,78 @@ launcher=$(psql_pg -tAc \
"SELECT count(*) FROM pg_stat_activity
WHERE backend_type ILIKE '%pg_cron%' OR application_name ILIKE '%pg_cron%'")
# so_telegraf before the postgres state lands, postgres after.
cron_db=$(docker exec so-postgres psql -U postgres -tAc \
"SELECT current_setting('cron.database_name', true)" | tr -d '[:space:]')
run_count=0
recent_ok=0
last_run=never
cron_installed=$(has_cron)
if [[ -n "$cron_installed" ]]; then
run=$(psql_cron -tAc \
"SELECT d.status || ' at ' || d.start_time
has_run_details=$([[ -n "$cron_db" ]] && docker exec so-postgres psql -U postgres -d "$cron_db" -tAc \
"SELECT to_regclass('cron.job_run_details') IS NOT NULL" 2>/dev/null || echo f)
if [[ "$has_run_details" == t ]]; then
runs=$(docker exec so-postgres psql -U postgres -d "$cron_db" -tA -F '|' -c \
"SELECT count(*),
count(*) FILTER (WHERE d.status = 'succeeded'
AND d.start_time > now() - make_interval(hours => $MAX_AGE_HOURS)),
coalesce((array_agg(format('%s at %s', d.status, d.start_time)
ORDER BY d.start_time DESC))[1], 'never')
FROM cron.job_run_details d JOIN cron.job j USING (jobid)
WHERE j.jobname = '$JOB_NAME'
ORDER BY d.start_time DESC LIMIT 1")
if [[ -n "$run" ]]; then
message=$(psql_cron -tAc \
"SELECT coalesce(d.return_message, '')
FROM cron.job_run_details d JOIN cron.job j USING (jobid)
WHERE j.jobname = '$JOB_NAME'
ORDER BY d.start_time DESC LIMIT 1")
message=$(sanitise <<<"$message")
message=${message//$'\n'/ }
last_run="$(sanitise <<<"$run"): ${message:0:200}"
fi
WHERE j.jobname = '$JOB_NAME'" 2>/dev/null) \
|| { echo "Could not read $JOB_NAME runs from cron.job_run_details; cannot judge maintenance health." >&2; exit 2; }
IFS='|' read -r run_count recent_ok last_run <<< "$runs"
fi
# An unreadable run history exits 2 rather than 1: soup repairs on 1, which
# truncates the default partitions, and a transient query error must not do that.
# No runs at all means a freshly scheduled job (or pg_cron not loaded, handled
# by restart_needed); the partition checks catch a job that never fires. Runs
# with no recent success mean the job fires but fails, e.g. cannot connect.
maintenance_failing=false
[[ "$run_count" -gt 0 && "$recent_ok" -eq 0 ]] && maintenance_failing=true
# A grid that has never written a metric has nothing to recover, and an empty
# cron_db means pg_cron is not loaded at all, which no restart fixes.
restart_needed=false
[[ "$launcher" -eq 0 && "$parents" -gt 0 && -n "$cron_db" ]] && restart_needed=true
# group_role registers the job only in the postgres database.
job_inactive=false
if [[ "$cron_db" == postgres ]]; then
[[ -z "$job" || "${job##*|}" == f ]] && job_inactive=true
fi
repair_needed=false
[[ "$stranded" -gt 0 ]] && repair_needed=true
[[ "$behind" -gt 0 ]] && repair_needed=true
[[ "$misconfigured" -gt 0 ]] && repair_needed=true
$restart_needed && repair_needed=true
$job_inactive && repair_needed=true
$maintenance_failing && repair_needed=true
echo "Telegraf partition status:"
print_report
echo "Default partitions with rows: $stranded"
echo "pg_cron metadata database: $(sanitise <<<"${cron_db:-unset}")"
[[ "$cron_db" == postgres ]] \
|| echo "pg_cron metadata database is $(sanitise <<<"${cron_db:-unset}"); not checking the maintenance job."
psql_tg -c "$REPORT"
echo "Rows stranded in default partitions: $stranded"
echo "pg_cron metadata database: ${cron_db:-unset}"
echo "pg_cron launcher running: $([[ "$launcher" -gt 0 ]] && echo yes || echo no)"
echo "Last $JOB_NAME run: $last_run"
echo
if ! $repair_needed; then
echo "Telegraf partitions are healthy. Nothing to do."
finish 0
exit 0
fi
if $CHECK_ONLY; then
echo "Repair is needed:"
[[ "$stranded" -gt 0 ]] && echo " * $stranded default partition(s) hold rows"
[[ "$stranded" -gt 0 ]] && echo " * $stranded row(s) stranded in default partitions"
[[ "$behind" -gt 0 ]] && echo " * $behind parent(s) with no partition for the current window"
[[ "$misconfigured" -gt 0 ]] && echo " * $misconfigured parent(s) with stale partman settings"
$restart_needed && echo " * pg_cron's launcher is dead; maintenance is not running at all"
$job_inactive && echo " * the maintenance job is missing or inactive"
$maintenance_failing && echo " * no successful $JOB_NAME run in the last $MAX_AGE_HOURS hour(s)"
echo
echo "Re-run without --check to repair."
finish 1
exit 1
fi
if [[ "$stranded" -gt 0 ]] && ! $ASSUME_YES; then
echo "This will permanently discard the rows in the $stranded default partition(s) above."
echo "This will permanently discard the $stranded stranded row(s) above."
$restart_needed && ! $NO_RESTART && \
echo "so-postgres will also be restarted, which briefly interrupts SOC."
[[ -t 0 ]] || { echo "Not a terminal; re-run with --yes to confirm." >&2; finish 1; }
[[ -t 0 ]] || { echo "Not a terminal; re-run with --yes to confirm." >&2; exit 2; }
read -r -p "Continue? [y/N] " answer
[[ "$answer" =~ ^[Yy]$ ]] || { echo "Aborted."; finish 0; }
fi
ERR_STATUS=1
ERR_PREFIX="Telegraf partition repair failed"
if [[ "$cron_db" == postgres ]]; then
if [[ -z "$job" ]]; then
echo "Could not register the maintenance job; run a highstate and try again." >&2
finish 3
fi
job_id=${job%%|*}
[[ "${job##*|}" == t ]] && restore_active=true || restore_active=false
echo "Pausing the $JOB_NAME job during the repair."
psql_cron -tAc "SELECT cron.alter_job($job_id, active := false)" >/dev/null
[[ "$answer" =~ ^[Yy]$ ]] || { echo "Aborted."; exit 0; }
fi
if [[ "$misconfigured" -gt 0 ]]; then
@@ -317,7 +201,7 @@ if [[ "$misconfigured" -gt 0 ]]; then
"UPDATE partman.part_config
SET premake = GREATEST(premake, $PREMAKE),
infinite_time_partitions = true
WHERE $TELEGRAF_PARENTS"
WHERE parent_table LIKE 'telegraf.%'"
fi
if [[ "$stranded" -gt 0 ]]; then
@@ -325,24 +209,27 @@ if [[ "$stranded" -gt 0 ]]; then
# One transaction: Telegraf is still writing, so a default emptied without
# its partition in place is refilled before maintenance can attach one.
psql_tg -v ON_ERROR_STOP=1 <<'EOSQL'
BEGIN;
DO $$
DECLARE
r record;
BEGIN
FOR r IN
SELECT pc.parent_table, d.default_part
FROM so_admin.telegraf_defaults() d
JOIN partman.part_config pc ON to_regclass(pc.parent_table) = d.parent_table
WHERE d.default_part IS NOT NULL
SELECT pc.parent_table,
format('%I.%I', n.nspname, c.relname) AS default_table
FROM partman.part_config pc
JOIN pg_class p ON p.oid = pc.parent_table::regclass
JOIN pg_inherits i ON i.inhparent = p.oid
JOIN pg_class c ON c.oid = i.inhrelid
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE pc.parent_table LIKE 'telegraf.%'
AND pg_get_expr(c.relpartbound, c.oid) = 'DEFAULT'
LOOP
EXECUTE format('TRUNCATE TABLE %s', r.default_part);
EXECUTE format('TRUNCATE TABLE %s', r.default_table);
PERFORM partman.create_partition_time(
r.parent_table, ARRAY[date_trunc('day', now())]::timestamptz[]);
END LOOP;
END
$$;
COMMIT;
EOSQL
fi
@@ -358,7 +245,7 @@ if $restart_needed; then
sleep 2
done
docker exec so-postgres pg_isready -U postgres -q \
|| { echo "so-postgres did not come back; check 'docker logs so-postgres'." >&2; finish 1; }
|| { echo "so-postgres did not come back; check 'docker logs so-postgres'." >&2; exit 1; }
fi
fi
@@ -374,14 +261,9 @@ CALL partman.run_maintenance_proc();
\endif
EOSQL
if [[ -n "$job_id" ]]; then
psql_cron -tAc "SELECT cron.alter_job($job_id, active := true)" >/dev/null
restore_active=
echo "The $JOB_NAME job is active."
fi
echo
echo "Telegraf partition status after repair:"
print_report
echo "The $JOB_NAME job runs hourly at :17. Confirm it fired with:"
psql_tg -c "$REPORT"
echo "The $JOB_NAME job runs hourly at :17. Until its next run succeeds, grid status"
echo "may still report so-postgres-maintenance as failed. Confirm it fired with:"
echo " so-telegraf-repair --check"
+7 -25
View File
@@ -138,10 +138,6 @@
# AGGREGATOR PLUGINS #
###############################################################################
[[aggregators.merge]]
drop_original = true
namepass = ["system", "swap", "kafka_partition"]
###############################################################################
# INPUT PLUGINS #
###############################################################################
@@ -293,7 +289,7 @@
password = "{{ salt['pillar.get']('elasticsearch:auth:users:so_logstash_user:pass') }}"
{%- endif %}
{% if grains.role in ['so-manager','so-managerhype','so-managersearch','so-standalone','so-receiver'] and GLOBALS.pipeline == "KAFKA" -%}
{% if grains.role in ['so-manager','so-managersearch','so-standalone','so-receiver'] and GLOBALS.pipeline == "KAFKA" -%}
[[inputs.jolokia2_agent]]
name_prefix= "kafka_"
urls = ["http://{{ NODEIP }}:8778/jolokia"]
@@ -350,26 +346,6 @@
interval = "60s"
{%- endif %}
{#- These report state that changes slowly; collecting them every interval only stores duplicate rows #}
{%- set SLOWSCRIPTS = [] %}
{%- for script in ['agentstatus.sh', 'os.sh', 'features.sh', 'raid.sh', 'zeekloss.sh'] %}
{%- if script in TELEGRAFMERGED.scripts[GLOBALS.role.split('-')[1]] %}
{%- do TELEGRAFMERGED.scripts[GLOBALS.role.split('-')[1]].remove(script) %}
{%- do SLOWSCRIPTS.append(script) %}
{%- endif %}
{%- endfor %}
{%- if SLOWSCRIPTS | length > 0 %}
[[inputs.exec]]
commands = [
{%- for script in SLOWSCRIPTS %}
["/scripts/{{script}}"]{% if not loop.last %},{% endif %}
{%- endfor %}
]
data_format = "influx"
timeout = "15s"
interval = "5m"
{%- endif %}
{%- if TELEGRAFMERGED.scripts[GLOBALS.role.split('-')[1]] | length > 0 %}
[[inputs.exec]]
commands = [
@@ -382,6 +358,12 @@
timeout = "15s"
{%- endif %}
{%- if salt['pillar.get']('healthcheck:enabled', False) %}
[[inputs.file]]
files = ["/host/nsm/zeek/logs/zeek_restart.log"]
data_format = "influx"
{%- endif %}
[[inputs.file]]
files = ["/etc/telegraf/node_config.json"]
name_override = "node_config"
+1 -1
View File
@@ -18,7 +18,7 @@
{% if GLOBALS.pipeline != 'REDIS' %}
{# When global pipeline is not REDIS remove redis.sh script. KAFKA metrics are collected via jolokia agent. Config in telegraf.conf #}
{% if GLOBALS.role in ['so-standalone', 'so-manager', 'so-managerhype', 'so-managersearch', 'so-receiver', 'so-heavynode'] %}
{% if GLOBALS.role in ['so-standalone', 'so-manager', 'so-managersearch', 'so-receiver', 'so-heavynode'] %}
{% do TELEGRAFMERGED.scripts[GLOBALS.role.split('-')[1]].remove('redis.sh') %}
{% endif %}
{% endif %}
+1 -1
View File
@@ -10,7 +10,7 @@
# if this script isn't already running
if [[ ! "`pidof -x $(basename $0) -o %PPID`" ]]; then
FILES=$(find /host/nsm/strelka/unprocessed -maxdepth 1 -type f | wc -l)
FILES=$(ls -1x /host/nsm/strelka/unprocessed | wc -l)
echo "faffiles files=$FILES"
fi
+2 -3
View File
@@ -12,9 +12,8 @@ if [[ ! "`pidof -x $(basename $0) -o %PPID`" ]]; then
CONTAINERSLOG=/var/log/somon/containers.log
# the collector rewrites this every minute; report nothing rather than repeating a stale
# file as if it were current, in case a run was skipped or the collector is wedged.
# Kept under two collector cycles so a 60s telegraf interval can't emit the same snapshot twice.
MAXAGE=90
# file as if it were current, in case a run was skipped or the collector is wedged
MAXAGE=150
if [ -r "$CONTAINERSLOG" ]; then
AGE=$(( $(date +%s) - $(stat -c %Y "$CONTAINERSLOG") ))
+18 -31
View File
@@ -11,40 +11,27 @@
if [[ ! "`pidof -x $(basename $0) -o %PPID`" ]]; then
PREVCOUNTFILE='/tmp/eps.txt'
STATS="$(curl -s localhost:9600/_node/stats)"
EVENTCOUNTCURRENT="$(curl -s localhost:9600/_node/stats | jq '.events.in')"
if echo "$STATS" | jq -e '.pipelines.search' > /dev/null 2>&1; then
STAGE=search
COUNT="$(echo "$STATS" | jq '.pipelines.search.events.out')"
elif echo "$STATS" | jq -e '.pipelines.receiver' > /dev/null 2>&1; then
STAGE=receiver
COUNT="$(echo "$STATS" | jq '.pipelines.receiver.events.in')"
else
exit 0
if [ ! -z "$EVENTCOUNTCURRENT" ]; then
if [ -f "$PREVCOUNTFILE" ]; then
EVENTCOUNTPREVIOUS=`cat $PREVCOUNTFILE`
else
echo "${EVENTCOUNTCURRENT}" > $PREVCOUNTFILE
exit 0
fi
echo "${EVENTCOUNTCURRENT}" > $PREVCOUNTFILE
# the division by 30 is because the agent interval is 30 seconds
EVENTS=$(((EVENTCOUNTCURRENT - EVENTCOUNTPREVIOUS)/30))
if [ "$EVENTS" -lt 0 ]; then
EVENTS=0
fi
echo "consumptioneps eps=${EVENTS%%.*}"
fi
if ! [[ "$COUNT" =~ ^[0-9]+$ ]]; then
exit 0
fi
NOW=$(date +%s)
read -r PREVEPOCH PREVCOUNT 2>/dev/null < "$PREVCOUNTFILE"
echo "${NOW} ${COUNT}" > "$PREVCOUNTFILE"
if ! [[ "$PREVEPOCH" =~ ^[0-9]+$ && "$PREVCOUNT" =~ ^[0-9]+$ ]]; then
exit 0
fi
ELAPSED=$((NOW - PREVEPOCH))
DELTA=$((COUNT - PREVCOUNT))
if [ "$ELAPSED" -le 0 ] || [ "$DELTA" -lt 0 ]; then
EPS=0
else
EPS=$((DELTA / ELAPSED))
fi
echo "consumptioneps,stage=${STAGE} eps=${EPS}"
fi
exit 0
+2 -1
View File
@@ -10,7 +10,8 @@ if [[ ! "`pidof -x $(basename $0) -o %PPID`" ]]; then
FPS_ENABLED=$(cat /var/log/sostatus/fps_enabled)
LKS_ENABLED=$(cat /var/log/sostatus/lks_enabled)
echo "features fps=$FPS_ENABLED,lks=$LKS_ENABLED"
echo "features fps=$FPS_ENABLED"
echo "features lks=$LKS_ENABLED"
fi
exit 0