edge_maintenance #19

  • //
  • guest/
  • russell_jackson/
  • sdp/
  • Maintenance/
  • edge_maintenance
  • View
  • Commits
  • Open Download .zip Download (6 KB)
#!/bin/bash
#
# weekly_maintenance.sh – perform routine Perforce cleanup and notifications.
#
# This script performs a series of maintenance tasks against a Helix Core
# installation using the SDP environment.  It logs all output to a file
# and emails failures to the configured MAILTO address. Client unloading
# is capped at 200 workspaces per run without a threshold alert.

# Fail fast on errors, unset variables and pipeline failures.
set -uo pipefail

# Determine the SDP instance either from the first positional parameter or
# the environment.  Require it to be set to avoid accidental misuse.
P4INSTANCE="${1:-${P4INSTANCE:-}}"
if [[ -z "$P4INSTANCE" ]]; then
  echo "P4PORT=${P4PORT:-unknown}: Instance parameter not supplied." >&2
  echo "You must supply the Perforce instance as a parameter to this script or set \$P4INSTANCE in the environment."
  exit 1
fi

# Initialize pyenv so scripts use the correct Python version.
export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init -)"

# Source SDP environment variables and ensure we can talk to Perforce.
if ! source "/p4/common/bin/p4_vars" "$P4INSTANCE"; then
  echo "P4PORT=${P4PORT:-unknown}: Unable to source p4_vars for instance $P4INSTANCE" >&2
  exit 1
fi

# sdp_structured_log.sh is standalone (no backup_functions.sh dependency) and
# only needs $LOGS, already exported by p4_vars above.
# shellcheck disable=SC1091
[[ -r /p4/common/bin/sdp_structured_log.sh ]] && source /p4/common/bin/sdp_structured_log.sh
declare -F sdp_log_init >/dev/null && sdp_log_init edge_maintenance

# Prepare our log file and client names.  Use the hostname and server name for uniqueness.
LOG="/p4/sdp/Maintenance/${HOSTNAME}_${P4SERVER}_log.txt"
P4CLIENT="${HOSTNAME}_unload"

# Ensure the maintenance working directory exists and change into it for relative paths.
MAINT_DIR="/p4/sdp/Maintenance"
if [[ ! -d "$MAINT_DIR" ]]; then
  echo "P4PORT=${P4PORT:-unknown}: Maintenance directory $MAINT_DIR does not exist." >&2
  exit 1
fi

# Both weekly entry points share one instance lock; each run gets its own
# reports and scratch files. The server.locks cleanup below is intentional.
source "$MAINT_DIR/maintenance_workspace.sh" || exit 1
maintenance_workspace_init "$P4INSTANCE" || {
  workspace_status=$?
  if (( workspace_status == 75 )); then exit 0; fi
  exit "$workspace_status"
}

# Acquire the instance lock before replacing the shared run log. A competing
# invocation must not rotate the active owner's diagnostics.
if declare -F sdp_prepare_run_log >/dev/null; then
  sdp_prepare_run_log "$LOG" || exit 1
  exec >"$LOG" 2>&1 || exit 1
else
  exec >>"$LOG" 2>&1 || exit 1
fi
printf 'Maintenance run directory: %s run_id=%s\n' "$SDP_MAINTENANCE_WORKDIR" "${SDP_RUN_ID:-unavailable}"

# Helper function to timestamp and append a message to the log.
log_msg() {
  local msg="${1:-}"
  printf '%s %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$msg" >>"$LOG"
}

# Tracks whether any step below actually failed, so the end-of-run email
# only fires on a real problem instead of on every single run (it was
# unconditional before - "Weekly maintenance log" landed in MAILTO's inbox
# every night regardless of outcome). Also mirrors into the structured
# jsonl record via sdp_set_status/sdp_set_error, since none of the `|| true`
# guards below were ever reporting failure to it either.
HAD_ERROR=0
run_step() {
  local desc="$1"
  shift
  log_msg "$desc"
  "$@" >>"$LOG" 2>&1
  local rc=$?
  if (( rc != 0 )); then
    log_msg "ERROR: $desc failed (exit $rc)"
    HAD_ERROR=1
    declare -F sdp_set_status >/dev/null && sdp_set_status failure
    declare -F sdp_set_error >/dev/null && sdp_set_error "$desc failed (exit $rc)"
  fi
  return "$rc"
}

# Start logging.
log_msg "--- Weekly maintenance started for instance $P4INSTANCE (P4PORT=${P4PORT:-unknown}, run_id=${SDP_RUN_ID:-unavailable}) ---"

# Authentication failures must be visible and stop all cleanup work.
run_step "Authenticating with p4login" /p4/common/bin/p4login || exit 1

# Remove any leftover server lock files.  This can prevent DB replays from
# succeeding if the previous run terminated uncleanly.
run_step "Removing server lock files from /p4/${P4INSTANCE}/root/server.locks" \
  rm -rf "/p4/${P4INSTANCE}/root/server.locks"

# Generate lists of inactive clients.  accessdates.py produces clients.txt
# based on the configured number of weeks.
REPORT_OK=0
if run_step "Generating client inactivity report via accessdates.py" \
  accessdates.py "$P4INSTANCE" --client-snapshot client_snapshot.json; then
  REPORT_OK=1
fi

# Count the number of clients to be unloaded and decide what to do.  Use
# wc -l with a redirection to avoid counting the filename itself.  Fall back
# to zero if the file is missing.
client_count=0
if (( REPORT_OK )) && [[ -f clients.txt ]]; then
  client_count=$(wc -l < clients.txt || echo 0)
fi
log_msg "Inactive client count: $client_count"
declare -F sdp_add_field >/dev/null && sdp_add_field inactive_client_count "$client_count"

# Unload at most 200 clients per run; leave any remainder for later runs.
if (( client_count > 0 )); then
  unload_count=$(( client_count < 200 ? client_count : 200 ))
  run_step "Unloading up to $unload_count clients via unload_clients.py" \
    unload_clients.py "$P4INSTANCE" --limit 200 --clients-file client_snapshot.json
fi

# Notify users about pending client deletions.  This script emails owners of
# workspaces that will be removed soon.  Only run once per maintenance window.
# run_step "Running email_pending_client_deletes.py" email_pending_client_deletes.py "$P4INSTANCE"

# Remove empty pending changelists.  These accumulate over time and can slow
# down server operations.
run_step "Removing empty pending changelists via remove_empty_pending_changes.py" \
  remove_empty_pending_changes.py "$P4INSTANCE"

# Finish logging. Only email MAILTO when something actually failed - the
# structured jsonl record (emitted automatically on exit by sdp_log_init's
# trap, status/error set by run_step above) is the routine, non-noisy
# record of every run; this email is reserved for the cases that need a
# human to look.
log_msg "--- Weekly maintenance completed run_id=${SDP_RUN_ID:-unavailable} ---"
if (( HAD_ERROR )) && [[ -n "${MAILTO:-}" ]]; then
  if ! mail -s "${HOSTNAME} ${P4SERVER} P4PORT=${P4PORT:-unknown} Weekly maintenance FAILED" "$MAILTO" <"$LOG"; then
    log_msg "ERROR: Unable to send maintenance failure notification"
  fi
fi

exit "$HAD_ERROR"
# Change User Description Committed
#19 34004 Russell C. Jackson (Rusty) Improve SDP backup/recovery, Maintenance, logging and cloud operation.

Add owned checkpoint locks, recovery-aware retention, preserved database switches,
bounded cleanup and coordinated AWS/Azure snapshots. Fix service-user password
initialization, edge staging, standby promotion state parsing, proxy cache setup,
typed depot verification and historical-server configuration/error handling.
Remove unused OTP programs; keep Windows removed, server.locks cleanup and the
journal-count condition unchanged. Add no global shell strict modes.

Update operational documentation and add reproducible mkdirs.sh/mkdirs.cfg
container assurance. Validate Ubuntu 26.04 and UBI 8.10/9.8/10.2 across both Python
runtimes: 44 stages; 428 offline tests per runtime/OS, zero offline skips;
19 deployed-archive comparisons per combination. Three six-node topology runs
pass all 30 recovery/failure scenarios. P4D 2023.1 through 2026.1 on UBI 8/9
pass all 14 cells and 98 scenarios. Static, documentation and provenance checks
pass. Containers do not certify every fleet kernel, patch build or integration.

Tested source manifest SHA-256:
e1a23e17b8ced8c1148d9a250589977c6f6e98606bf2a81dc3ff5d7c909e3695
Deployed comparison archive SHA-256:
1bd23217356ee3808e040d39aea5be9519bda7c8f2b4924656a70aff8ad7293a
#18 34003 Russell C. Jackson (Rusty) Improve SDP diagnostics and correct maintenance/recovery failures.

Add durable logs and explicit failure handling across operational, Maintenance,
setup, trigger, snapshot and download scripts. Correct retention selection,
restore preflights, environment setup and command/API handling. Include offline
regression coverage and the review report with deferred improvement proposals.

Include requested journal archiving and automatic acceptance of new SSH host
keys for remote rsync. Preserve the NumJnls > 2 threshold and existing shell
options. Remove Windows scripts as requested.

Validation: full review suite 138 passed, 8 skipped; subsequent rsync/logging
suite 29 passed. Shell/Python/JSON static checks passed; no live operations run.
#17 33803 Russell C. Jackson (Rusty) Cap maintenance client unloading at 200 workspaces per run without threshold alerts; pass the SDP instance and limit to unload_clients.py.
#16 33799 Russell C. Jackson (Rusty) Include P4PORT in cron diagnostics and SDP alert logging
#15 33760 Russell C. Jackson (Rusty) Commenting out email warnings for now.
#14 33526 Russell C. Jackson (Rusty) Fix unconditional maintenance emails; add jsonl structured logging across Maintenance/

- Maintenance/maintenance and Maintenance/edge_maintenance (run daily via
  cron, despite the "weekly" naming) were emailing the full run log on
  every single run, success or not, because every step was guarded with
  `|| true` and never reported failure anywhere. Added a run_step helper
  that tracks real failures and wires them into sdp_set_status/
  sdp_set_error; the end-of-run email now only fires when something
  actually failed (subject changed to "... FAILED" to make that obvious).

- Every other script in Maintenance/ (39 Python scripts + create_p4_filelist.sh
  + email.sh; sdputils.py excluded as a pure library with no __main__) now
  emits the same jsonl structured-logging record as the cron scripts in
  common/bin, via sdp_structured_log.py's run() context manager (auto-emits
  success/failure on exit, sys.exit(N), or unhandled exception) or, for the
  two shell scripts, sdp_structured_log.sh's sdp_log_init (auto ERR/EXIT
  trap coverage).

- remove_empty_pending_changes.py: the initial pending-changes listing
  failure path did a bare `return` (swallowed by the auto-emit machinery,
  since a normal return maps to exit code 0/success); now returns 1, and
  per-change delete failures inside the loop also flip the overall exit
  code, so a partially-failed run is correctly logged as a failure instead
  of a silent success.

- pymail.py: usage() called a bare sys.exit() (exit code 0) for both the
  -h/help path AND real bad-invocation paths (missing required args,
  getopt errors) - meaning a bad invocation reported success both to the
  OS and to the structured log. usage() now takes an explicit code
  (default 0, preserving -h's exit-0 convention); the two real error call
  sites pass a nonzero code.
#13 33459 Russell C. Jackson (Rusty) Fix cron mail noise from p4login output and unconditional success emails; add sdp_log_init jsonl logging (with matching log rotation) to remaining SDP maintenance scripts; support multiple limits groups in update_limits.py; exempt super users from keep_group_unset.py trigger; remove dead crontab entries
#12 32442 Russell C. Jackson (Rusty) Added the pyenv environment set up to the maintenance scripts.
#11 31928 Russell C. Jackson (Rusty) Removed set -e from scripts.
#10 31836 Russell C. Jackson (Rusty) Added limit on the number of clients to unload
#9 31545 Russell C. Jackson (Rusty) Removed delete of unloaded clients from edge maintenance.
#8 30939 Russell C. Jackson (Rusty) Added delete_unload_clients.py to maintenance
#7 29744 Russell C. Jackson (Rusty) Redirect output to dev null to manage the size of the log file.
#6 29104 Russell C. Jackson (Rusty) Update remove_empty to use ztag and added the script to the edge_maintenance.
#5 28680 Russell C. Jackson (Rusty) Changed sh to bash
#4 28419 Russell C. Jackson (Rusty) Fixed edge maintenance log path.
#3 24713 Russell C. Jackson (Rusty) Removed specific client data.
#2 24675 Russell C. Jackson (Rusty) Fixed bugs in sdputils.py and scripts using it.
Converted to standard 2 space spacing, removed copyright stuff.
#1 24075 Russell C. Jackson (Rusty) Added maintenance and edge_maintenance to crontab, created edge_maintenance and updated maintenance.