edge_maintenance #15

  • //
  • 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 the results to the configured MAILTO address.  If a large
# number of clients are queued for unloading, it alerts the administrator
# instead of attempting the unload.

# 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 "Instance parameter not supplied."
  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 "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

# Authenticate using the SDP helper.  Suppress output but do not fail the script if login fails.
/p4/common/bin/p4login >/dev/null 2>&1 || true

# 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 "Maintenance directory $MAINT_DIR does not exist." >&2
  exit 1
fi
cd "$MAINT_DIR"

# 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 0
}

# Start logging.
log_msg "--- Weekly maintenance started for instance $P4INSTANCE ---"

# 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.
run_step "Generating client inactivity report via accessdates.py" \
  accessdates.py "$P4INSTANCE"

# 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 [[ -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"

# If there are fewer than 200 clients queued, unload them.  Otherwise alert
# the administrator by emailing the list of clients rather than unloading
# them, as processing a very large set may impact server performance.
if (( client_count > 0 )); then
  if (( client_count < 200 )); then
    run_step "Unloading $client_count clients via unload_clients.py" \
      unload_clients.py
  else
    log_msg "Too many clients ($client_count) to unload automatically; sending alert email instead."
    declare -F sdp_set_status >/dev/null && sdp_set_status warning
    declare -F sdp_add_field >/dev/null && sdp_add_field unload_threshold_exceeded true
    if [[ -n "${MAILTO:-}" ]]; then
      mail -s "${HOSTNAME} ${P4SERVER} client unload threshold exceeded" "$MAILTO" < clients.txt || true
    fi
  fi
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 ---"
if (( HAD_ERROR )) && [[ -n "${MAILTO:-}" ]]; then
  mail -s "${HOSTNAME} ${P4SERVER} Weekly maintenance FAILED" "$MAILTO" <"$LOG" || true
fi

# Change User Description Committed
#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.