snapshot.sh #1

  • //
  • p4-sdp/
  • r26.1.0.BETA/
  • Server/
  • Unix/
  • p4/
  • common/
  • cloud/
  • aws/
  • bin/
  • snapshot.sh
  • View
  • Commits
  • Open Download .zip Download (6 KB)
#!/bin/bash
set -u

# Version ID Block. Relies on +k filetype modifier.
#------------------------------------------------------------------------------
# shellcheck disable=SC2016
declare VersionID='$Id: //p4-sdp/r26.1.0.BETA/Server/Unix/p4/common/cloud/aws/bin/snapshot.sh#1 $ $Change: 33444 $'
declare VersionStream=${VersionID#*//}; VersionStream=${VersionStream#*/}; VersionStream=${VersionStream%%/*};
declare VersionCL=${VersionID##*: }; VersionCL=${VersionCL%% *}
declare Version=${VersionStream}.${VersionCL}
[[ "$VersionStream" == r* ]] || Version="${Version^^}"

# Get EBS volumes with Name tag of <host>-root, <host>-p4depots, and <host>-p4logs,
# e.g. perforce-01-root, perforce-01-p4depots, and perforce-01-p4logs. Take snapshots,
# and tag them with the current journal counter.
#
# IMPORTANT: These are the exact "Name" tag values this script looks for on
# real AWS EBS volumes. They must be kept in sync with whatever "Name" tags
# are actually applied to the EBS volumes backing live production P4 servers
# -- if a volume's real tag doesn't match one of these, this script will not
# find it and will skip snapshotting it (see the errmsg() below; not
# completely silent, but easy to miss if nobody's watching cron output).
# When the SDP's default mount-point naming changes (as it did for 2026.1,
# /hxdepots -> /mnt/p4depots and /hxlogs -> /mnt/p4logs, see SDP-1379),
# real, already-deployed EBS volumes tagged under the old convention need to
# either be re-tagged to match, or -- as a stopgap -- recognized via
# LegacyVolumeBaseName below so they don't silently stop being backed up.

declare ThisScript="${0##*/}"
declare ThisHost=${HOSTNAME%%.*}
declare VolumeBaseName=
declare VolumeName=
declare LegacyVolumeName=
declare SnapshotName=
declare VolumeId=
declare SnapshotId=
declare Cmd=
declare -i ExitCode=0
declare CurrentJournal=
declare SnapshotAgeToExpire=
declare CurrentDate=

# One release's worth of backward compatibility for EBS volumes not yet
# re-tagged after the 2026.1 mount-name rebrand (see the comment above).
# Maps the current volume-base-name to the legacy name it replaced; "root"
# has no legacy alias, since its name didn't change.
declare -A LegacyVolumeBaseName=( [p4depots]=hxdepots [p4logs]=hxlogs )

function msg () { echo -e "$*"; }
function errmsg () { msg "\\nError: ${1:-Unknown Error}\\n"; ExitCode=1; }
function warnmsg () { msg "\\nWarning: ${1:-Unknown Warning}\\n"; }
function bail () { errmsg "${1:-Unknown Error}"; exit "${2:-1}"; }

# Login to the server so the next command will work properly when testing the script in isolation
/p4/common/bin/p4login

# Get the latest journal version from the db.counters database
CurrentJournal=$("$P4DBIN" -r "$P4ROOT" -k db.counters -jd - 2>&1 | grep @journal@ | cut -d '@' -f 8)

# Set the 'aging off' duration, in days; snapshots older than this will be deleted
SnapshotAgeToExpire=90

msg "Started ${0##*/} version $Version at $(date)."

for VolumeBaseName in root p4depots p4logs; do
   VolumeName="${ThisHost}-${VolumeBaseName}"
   VolumeId=$(aws ec2 describe-volumes --filters Name=tag:Name,Values="$VolumeName" --query 'Volumes[*].{ID:VolumeId}' --output text)

   if [[ -z "$VolumeId" && -n "${LegacyVolumeBaseName[$VolumeBaseName]:-}" ]]; then
      LegacyVolumeName="${ThisHost}-${LegacyVolumeBaseName[$VolumeBaseName]}"
      VolumeId=$(aws ec2 describe-volumes --filters Name=tag:Name,Values="$LegacyVolumeName" --query 'Volumes[*].{ID:VolumeId}' --output text)
      if [[ -n "$VolumeId" ]]; then
         warnmsg "Volume tagged '$LegacyVolumeName' (legacy Name tag) instead of '$VolumeName'. Using it for now, but please re-tag this volume to '$VolumeName' -- this fallback is only intended to be temporary."
         VolumeName="$LegacyVolumeName"
      fi
   fi

   if [[ -z "$VolumeId" ]]; then
      errmsg "Could not determine VolumeId for $VolumeName. Skipping it."
      continue
   fi

   msg "Snapshotting volume $VolumeName [$VolumeId]."

   Cmd=$(aws ec2 create-snapshot --description "${VolumeName} snapshot created by ${ThisScript} on main p4d instance" --volume-id "$VolumeId" --query SnapshotId --output text)
   msg "Running: ${Cmd[*]}"
   SnapshotId=$("${Cmd[@]}")

# The tag "Contents" with value "Perforce Checkpoints and Archives" is used in the following section
# to identify snapshots that are specific to Perforce, so they can be deleted when they expire.

   if [[ -n "$SnapshotId" ]]; then
      msg "Snapshot created for $VolumeName on $ThisHost with Id: $SnapshotId."
      SnapshotName="${VolumeName}-${CurrentJournal}"
      Cmd=$(aws ec2 create-tags --resources "$SnapshotId" \
         --tags Key=Host,Value="$ThisHost" \
                Key=Contents,Value="Perforce Checkpoints and Archives" \
                Key=Name,Value="$SnapshotName" \
                Key=Backup,Value="true" \
                Key=Lifecycle,Value="Managed by /p4/common/cloud/aws/bin/snapshot.sh on main p4d instance" \
                --output text)
      if "${Cmd[@]}"; then
         msg "Verified: Resource tags applied."
      else
         errmsg "Failed to apply tags to Snapshot $SnapshotId."
      fi
   else
      errmsg "Failed to create snapshot for $VolumeName."
   fi
done

#############################################################
# This section automatically deletes snapshots that are     #
# older than $SnapshotAgeToExpire days. This replaces       #
# the lifecycle management portion of AWS's Snapshot        #
# automation, since the snapshots are generated externally. #
#############################################################

# Fetching snapshot IDs and their creation dates where tag Contents is 'Perforce Checkpoints and Archives'
snapshots_to_check=$(aws ec2 describe-snapshots --query "Snapshots[?Tags[?Key=='Contents' && Value=='Perforce Checkpoints and Archives']].[SnapshotId,StartTime]" --output text)

CurrentDate=$(date +%s)

while read -r snapshot_id creation_date; do
    # Converting snapshot creation date to seconds
    snapshot_date=$(date -d "$creation_date" +%s)

    # Calculate age of snapshot
    snapshot_age=$(( (CurrentDate - snapshot_date) / 86400 )) # 86400 seconds in a day

    # Check if snapshot is older than specified days
    if [ "$snapshot_age" -ge "$SnapshotAgeToExpire" ]; then
        echo "Deleting snapshot $snapshot_id which is $snapshot_age days old."
        aws ec2 delete-snapshot --snapshot-id "$snapshot_id"
    fi
done <<< "$snapshots_to_check"

if [[ "$ExitCode" -eq 0 ]]; then
   msg "Processing completed OK."
else
   msg "Processing completed WITH ERRORS. Review the output above."
fi

exit $ExitCode
# Change User Description Committed
#1 33444 Claude (AI Agent by Anthropic) Initial population of r26.1.0.BETA from main.
//p4-sdp/main/Server/Unix/p4/common/cloud/aws/bin/snapshot.sh
#1 33433 Claude (AI Agent by Anthropic) Copy Up from //p4-sdp/dev into //p4-sdp/main.

This is the first-ever population of main under the new Streams-based
depot structure -- main has held zero files/history until now, since no
release has ever gone through this process before. 463 files, covering
the entire 2026.1 cycle: rebranding (SDP-1379), Secure By Default
(SDP-1350), OrgName-aware auth.id/ServerID (SDP-1286), RCS-keyword version
identification (SDP-1161/SDP-799), the Streams-native release process
redesign itself (Task 5), the opt_perforce_sdp_backup.sh false-error fix,
the P4D 2026.1 test-suite targeting, refreshed P4*.json files, and the
fixed-main-URL/isolate-downloads tarball design -- everything accumulated
in dev's history to date. Isolated paths (ai_dev_support/, Version,
doc/*.html, doc/*.pdf, doc/gen/*.man.txt, doc/gen/sdp_install.cfg,
Unsupported/doc/*.html, Unsupported/doc/*.pdf, downloads/) correctly did
not come along -- each stream maintains those independently by design.

Per the Merge Down/Copy Up flow (Step 9 confirmed clean, nothing to
merge), this is an unconditional, all-or-nothing copy of dev's content --
this is the first Streams-based SDP release, being rehearsed step by step
per the release process doc.

Agent: Claude Code, Model: Claude Sonnet 5 (claude-sonnet-5), operating as bot_Claude_Anthropic.
//p4-sdp/dev/Server/Unix/p4/common/cloud/aws/bin/snapshot.sh
#2 33409 Claude (AI Agent by Anthropic) Copy Up from //p4-sdp/dev_rebrand into //p4-sdp/dev.

This is the first promotion of dev_rebrand's work into dev since
dev_rebrand was created (2025-05-24) -- 303 files, covering the entire
2026.1 rebranding effort (SDP-1379), the Secure By Default adaptation
(SDP-1350), OrgName-aware auth.id/ServerID (SDP-1286), RCS-keyword
version identification (SDP-1161/SDP-799), and the Streams-native release
process redesign (Task 5) done this session, plus everything else
accumulated in dev_rebrand's history before this session.

Per the Merge Down/Copy Up flow, this is intentionally a full,
unconditional blast-replace of dev's content from dev_rebrand -- all
selectivity/care happened in the preceding Merge Down (dev -> dev_rebrand,
changes 33407-33408), which absorbed Robert Cowham's independent dev-side
work first so nothing of his is lost by this Copy Up.

Two files are worth calling out since they might look alarming in
isolation:
- tools/mdcu.sh is deleted -- intentional, retired this session in favor
  of the two direct Streams commands now documented in
  doc/ReleaseProcessOverview.md.
- tools/ReleaseProcessOverview.md is deleted -- this is a stale relic of
  a file move dev_rebrand made back in 2025-05-24 (tools/ -> doc/) that
  was never previously propagated to dev; the current, fully-rewritten
  doc/ReleaseProcessOverview.md is added/updated correctly by this same
  changelist.
#1 31397 C. Thomas Tyler Populate -b SDP_Classic_to_Streams -s //guest/perforce_software/sdp/...@31368.
//guest/perforce_software/sdp/dev/Server/Unix/p4/common/cloud/aws/bin/snapshot.sh
#6 29942 C. Thomas Tyler Customer Contributed changes:

1. I use a slightly different method to get the latest journal version (for appending to the snapshot name). The approach I had been using was running into some issues with the Perforce account not being logged into the Perforce server, so I borrowed the approach out of the existing SDP scripts to login and retrieve that value.

2. I've added a section at the end to automatically delete old snapshots when they age past a value that's configurable in the script. If snapshots were being generated automatically by AWS automation, this aging-off process would be part of that; since we're pushing the snapshot creation from the Perforce server, I needed to add this functionality, so I didn't have to go manually delete old versions from AWS occasionally.
#5 29941 C. Thomas Tyler Contributed changes to snapshot.sh:
* Added /hxlogs to list of snapshotted volumes.
* Added explicit output formatting (text) to AWS CLI calls.
#4 27722 C. Thomas Tyler Refinements to @27712:
* Resolved one out-of-date file (verify_sdp.sh).
* Added missing adoc file for which HTML file had a change (WorkflowEnforcementTriggers.adoc).
* Updated revdate/revnumber in *.adoc files.
* Additional content updates in Server/Unix/p4/common/etc/cron.d/ReadMe.md.
* Bumped version numbers on scripts with Version= def'n.
* Generated HTML, PDF, and doc/gen files:
  - Most HTML and all PDF are generated using Makefiles that call an AsciiDoc utility.
  - HTML for Perl scripts is generated with pod2html.
  - doc/gen/*.man.txt files are generated with .../tools/gen_script_man_pages.sh.

#review-27712
#3 26843 C. Thomas Tyler Updated to adapt to changes in AWS CLI.
#2 25108 C. Thomas Tyler Corrected comments; no functional change.
#1 25104 C. Thomas Tyler Added sample script to create EBS snapshot of volumes with a Name
tag of <host>-root and <host>-hxdepots, e.g. perforce-01-root and
perforce-01-hxdepots.

This is intended to be called at the optimal time to reduce risk
exposure.  The optimal time is immediately after a journal rotation
completes near the start of the overall daily checkpoint process,
or optionally immediately after the offline checkpoint is created.

This script creates 2 EBS snapshots with appropriate resource
tagging each time it is run.

Note that a full recovery would entail mounting these 2 volumes,
creating new hxdepots and hxmetadata volumes, finalizing the
SDP structure, etc.  This is fairly straightforward, but not trival,
and is needed only as a Plan B for recovery.  Plan A is to use
Perforce replication to a secondary instance for fast and easier
recovery.

Basic data retention polices can be implemented with EBS Data
Lifecycle Policies.  Custom automation can copy recovery assets to
S3 Glacier for long term storage.

In addition to this script, a new high-level SDP structure is created,
/p4/common/cloud.  Under the new cloud directory is a directory for
the cloud provider, e.g. one of aws, azure, gcp, rackspace, etc.
Cloud-provider specific files can go in there.