#!/bin/bash set -u #============================================================================== # Copyright and license info is available in the LICENSE file included with # this package. #------------------------------------------------------------------------------ #============================================================================== # Declarations and Environment declare -i ErrorCount=0 declare -i WarningCount=0 declare -i Debug=0 declare -i NoOp=1 declare ThisScript=${0##*/} declare ThisUser= declare ThisHost=${HOSTNAME%%.*} declare Version=1.0.2 declare CmdLine="$0 $*" declare LogsDir="${LOGS:-${HOME:-/tmp}}" declare LogDatestamp= declare LogLink= declare Log= declare H1="==============================================================================" declare H2="------------------------------------------------------------------------------" declare RequiredUtils="awk date grep id ls tail wc" declare AccessLevel= declare CurrentServerServices= declare EdgeServerServices= declare OldEdgeServerID= declare NewEdgeServerID= declare SDPInstance=${SDP_INSTANCE:-} declare SDPEnv=/p4/common/bin/p4_vars declare SDPInstanceVars= declare TmpFile= #============================================================================== # Local Functions function msg () { echo -e "$*"; } function dbg () { [[ "$Debug" -eq 0 ]] || msg "DEBUG: $*"; } function errmsg () { msg "\\nError: ${1:-Unknown Error}\\n"; ErrorCount+=1; } #function warnmsg () { msg "\\nWarning: ${1:-Unknown Warning}\\n"; WarningCount+=1; } function bail () { errmsg "${1:-Unknown Error}"; exit "$ErrorCount"; } #------------------------------------------------------------------------------ # Function: terminate # shellcheck disable=SC2317 function terminate { # Disable signal trapping. trap - EXIT SIGINT SIGTERM dbg "$ThisScript: EXITCODE: $ErrorCount" # Stop logging. [[ "$Log" == off ]] || msg "\\nLog is: $Log\\n${H1}" # With the trap removed, exit. exit "$ErrorCount" } #------------------------------------------------------------------------------ # Function: usage (documentation) # # Input: # $1 - style, '-h' (for short usage synopsis) or '-man' (for full man-page # documentation). Default is -h. # # $2 - error message (optional). Specify this if usage() is called due to # usage error, in which case the given message displayed first, followed by # the standard usage message (short or long depending on $1). If displaying an # error, usually $1 should be -h so that the longer usage message doesn't # obscure the supplied usage error message. # # Sample Usage: # usage # usage -h # usage -man # usage -h "Incorrect command line usage." #------------------------------------------------------------------------------ function usage { declare style=${1:--h} declare errorMessage=${2:-} [[ -n "$errorMessage" ]] && \ errmsg "\\n\\nUsage Error:\\n\\n$errorMessage\\n\\n" msg "USAGE for $ThisScript v$Version: $ThisScript -old <OldEdgeServerID> -new <NewEdgeServerID> [-L <log>] [-y] [-d|-D] or $ThisScript [-h|-man] " if [[ $style == -man ]]; then msg " DESCRIPTION: This script renames an existing edge server. WARNING: THIS IS NOT A STANDARD OPERATION! This is only suitable for operation during a maintenance window when users are prevented from accessing systems (e.g. via Protections table lockout). If downtime for the commit server (and any other edege servers) must be minimized, the downtime needed for the commit and other edges can be small. However, the renamed edge server will need additional downtime due to the need to initialize it's checkpoint. OPERATIONAL PROCEDURE: This script must be executed on the commit server. The start state for this script is: * A 'p4 protects -m' returns super. * There is an operating commit server and edge server. * The edge server to be renamed has an ExternalAddress field value that is correct that and can be used such that 'super' is returned when the following is run: p4 -p <EdgeServerExternalAddress> protects -m * The edge is currently replicating OK per: p4 -p <EdgeServerExternalAddress> pull -ljv * The hostname component of the edge server to be renamed has an ExternalAddress field value that can be used with ssh, e.g. to do 'ssh edge_host echo hello' without prompting for a password. This will rename the ServerID of the edge by doing the following: * Generate a new server spec and configurables. * Generate and apply a journal patch to adjust client specs associated with the old edge ServerID, associating them with the new ServerID. * SSH to the edge server to create SDP directories and symlinks needed for the new ServerID. * Take an initial live checkpoint on the new edge server. OPTIONS: -L <log> Specify the path to a log file, or the special value 'off' to disable logging. By default, all output (stdout and stderr) goes to $LogsDir/${ThisScript%.sh}.<Datestamp>.log If -L is not used, a log link is updated such that a symlink $LOGS/${ThisScript%.sh}.log will point to the datestamped log. NOTE: This script is self-logging. That is, all standard and error output displayed is also captured in the log file. Use of 'tee' and redirection with '>' and '2>&1' are unnecessary. -si Operate silently. All output (stdout and stderr) is redirected to the log only; no output appears on the terminal. This cannot be used with '-L off'. This is useful when running from cron, as it prevents automatic email from being sent by cron directly, as it does when a script called from cron generates any output. This script is then responsible for email handling, if any is to be done. -y Live operation mode. By default, this script does a dry run, displaying commands that would be executed rather than running them. DEBUGGING OPTIONS: -d Enable additional debug output. -D Set extreme debugging verbosity using bash 'set -x' mode. Implies '-d'. HELP OPTIONS: -h Display short command line usage synopsis. -man Display this documentation manual page. EXAMPLES: Example 1: Standard usage (Dry Run): $ThisScript -old p4d_edge_syd -new p4d_edge_mlb Example 2: Standard usage (Live Run) $ThisScript -old p4d_edge_syd -new p4d_edge_mlb -y SEE ALSO: See also: convert_edge_to_commit.sh " fi exit 2 } #------------------------------------------------------------------------------ # Function: get_old_log_timestamp ($log) # # Get the last modified timestamp of the old log in a cross-platform manner. # If we don't get a correct value using 'stat' (which varies across the # UNIX/Linux/MacOSX spectrum), use the current time as a fallback. In that # case, the timestamp will reflect the time the log was moved rather than when # it was last modified, but that's still reasonable. The file timestamp will # still have the correct last-modified time. #------------------------------------------------------------------------------ function get_old_log_timestamp () { local log=${1:-} local oldLogTimestamp= [[ -n "$log" ]] || return if [[ "$(uname -s)" == "Darwin" ]]; then oldLogTimestamp=$(stat -L -f %Sm -t '%Y-%m-%d-%H%M%S' "$log" 2>/dev/null) else oldLogTimestamp="$(stat -L -c '%10y' "$log" | sed -e 's@[.].*$@@g' -e 's@:@@g' -e 's@ @-@g')" fi [[ "$oldLogTimestamp" =~ ^[2-9]{1}[0-9]{3}- ]] || oldLogTimestamp=$(date +'%Y-%m-%d-%H%M%S') echo "$oldLogTimestamp" } #============================================================================== # Command Line Processing declare -i shiftArgs=0 set +u while [[ $# -gt 0 ]]; do case $1 in (-h) usage -h;; (-V) msg "$ThisScript v$Version"; exit 2;; (-old) OldEdgeServerID="$2"; shiftArgs=1;; (-new) NewEdgeServerID="$2"; shiftArgs=1;; (-i) SDPInstance="$2"; shiftArgs=1;; (-man) usage -man;; (-y) NoOp=0;; (-L) Log="$2"; shiftArgs=1;; (-d) Debug=1;; # Debug mode. (-D) Debug=1; set -x;; # Use bash 'set -x' extreme debugging mode. (-*) usage -h "Unknown option: ($1)";; (*) usage -h "Unknown parameter: ($1)";; esac # Shift (modify $#) the appropriate number of times. shift; while [[ $shiftArgs -gt 0 ]]; do [[ $# -eq 0 ]] && usage -h "Incorrect number of arguments." shiftArgs=$shiftArgs-1 shift done done set -u #============================================================================== # Command Line Verification if [[ -z "$Log" ]]; then LogDatestamp=$(date +'%Y-%m-%d-%H%M%S') LogLink="${LogsDir}/${ThisScript%.sh}.log" Log="${LogsDir}/${ThisScript%.sh}.${LogDatestamp}.log" fi [[ -n "$SDPInstance" ]] || usage -h "The SDP instance must be defined; either do 'source /p4/common/bin/p4_vars N' (where N is the SDP instance name)' or else use '-i <N>'." SDPInstanceVars="/p4/common/config/p4_${SDPInstance}.vars" [[ -r "$SDPInstanceVars" ]] || usage -h "Invalid SDP instance name; missing instance vars file: $SDPInstanceVars" [[ -n "$OldEdgeServerID" ]] || usage -h "The '-old <OldEdgeServerID>' parameter is required." [[ -n "$NewEdgeServerID" ]] || usage -h "The '-new <NewEdgeServerID>' parameter is required." #============================================================================== # Main Program # shellcheck disable=SC1090 source "$SDPEnv" "$SDPInstance" ||\ bail "Could not do: /p4/common/bin/p4_vars \"$SDPInstance\"" for u in $RequiredUtils; do [[ -n "$(command -v "$u")" ]] || errmsg "Missing required utility: $u" done [[ "$ErrorCount" -eq 0 ]] || bail "Aborted early due to missing required utilitiies." trap terminate EXIT SIGINT SIGTERM if [[ "$Log" != off ]]; then if [[ -e "$Log" ]]; then # shellcheck disable=SC2012 OldLogTimestamp="$(ls -l --time-style +'%Y%m%d-%H%M%S' "$Log" 2>/dev/null | awk '{print $6}')" # shellcheck disable=SC2012 [[ -n "$OldLogTimestamp" ]] ||\ OldLogTimestamp="$(ls -l -D '%Y%m%d-%H%M%S' "$Log" 2>/dev/null | awk '{print $6}')" [[ -n "$OldLogTimestamp" ]] ||\ OldLogTimestamp="$(date +'%Y%m%d-%H%M%S' "$Log" 2>/dev/null)" [[ -n "$OldLogTimestamp" ]] ||\ OldLogTimestamp="old" OldLog="${LogsDir}/${ThisScript%.sh}.${OldLogTimestamp}.log" mv -f "$Log" "$OldLog" || bail "Could not do: mv -f \"$Log\" \"$OldLog\"" dbg "Rotated $Log to $OldLog" fi if [[ -n "$LogLink" && -e "$LogLink" ]]; then if [[ -L "$LogLink" ]]; then rm -f "$LogLink" else # If the name that should be a symlink is not a symlink, move it aside before # creating the symlink. OldLogTimestamp=$(get_old_log_timestamp "$LogLink") mv -f "$LogLink" "${LogLink%.log}.${OldLogTimestamp}.log" ||\ bail "Could not move old log file aside; tried: mv -f \"$LogLink\" \"${LogLink%.log}.${OldLogTimestamp}.log\"" fi fi touch "$Log" || bail "Couldn't touch log file [$Log]." if [[ -n "$LogLink" ]]; then # Use a subshell so the 'cd' doesn't persist. ( cd "$LogsDir"; ln -s "${Log##*/}" "${LogLink##*/}"; ) ||\ bail "Couldn't initialize log symlink; tried: ln -s \"$Log\" \"$LogLink\"" fi # Redirect stdout and stderr to a log file. exec > >(tee "$Log") exec 2>&1 msg "${H1}\\nLog is: $Log\\n" fi ThisUser=$(id -n -u) msg "Started $ThisScript v$Version as $ThisUser@$ThisHost at $(date).\\nCommand Line was: $CmdLine" if [[ "$NoOp" -eq 0 ]]; then msg "Operating in LIVE mode." else msg "NO_OP: Operating in DRY RUN mode." fi msg "Renaming edge ServerID from '$OldEdgeServerID' to '$NewEdgeServerID'." msg "${H2}\\nStarting Preflight Checks." CurrentServerServices=$("$P4BIN" -ztag -F %serverServices% info -s 2>/dev/null) if [[ "$CurrentServerServices" == commit-server ]]; then msg "Verified: Current server is the commit server." else errmsg "Current server services is '$CurrentServerServices'; must be 'commit-server'." fi AccessLevel=$("$P4BIN" protects -m 2>/dev/null) if [[ -n "$AccessLevel" ]]; then if [[ "$AccessLevel" =~ ^super$ ]]; then msg "Verified: User $P4USER has super access." else errmsg "User $P4USER has only '$AccessLevel' access; 'super' is required." fi else errmsg "Could not verify access level; super required." fi EdgeServerServices=$("$P4BIN" -ztag -F %Services% server -o "$OldEdgeServerID" 2>/dev/null) if [[ "$EdgeServerServices" == edge-server ]]; then msg "Verified: Old edge ServerID has services edge-server." else errmsg "Old edge ServerID has services '$EdgeServerServices'; must be edge-server to use with $ThisScript." fi TmpFile=$(mktemp) if "$P4BIN" server --exists -o "$NewEdgeServerID" > "$TmpFile" 2>&1; then errmsg "New edge ServerID '$NewEdgeServerID' already exists. It must not exist at the start of processing." else if grep -E -q '^Server .* doesn.t exist' "$TmpFile"; then msg "Verified: New edge ServerID '$NewEdgeServerID' does not yet exist." else errmsg "Could not verify that new edge ServerID '$NewEdgeServerID' does not yet exist." fi fi if [[ "$ErrorCount" -eq 0 ]]; then msg "\\nAll preflight checks passed. Proceeding." else bail "Aborting due to failed preflight checks. See errors reported above." fi if [[ $ErrorCount -eq 0 && $WarningCount -eq 0 ]]; then msg "${H2}\\nAll processing completed successfully.\\n" elif [[ $ErrorCount -eq 0 ]]; then msg "${H2}\\nProcessing completed, but with $WarningCount warning. Review the output above carefully.\\n" else msg "${H2}\\nProcessing completed, but with $ErrorCount errors and $WarningCount warnings. Scan above output carefully.\\n" fi # Display runtime. msg "That took $((SECONDS/3600)) hours $((SECONDS%3600/60)) minutes $((SECONDS%60)) seconds.\\n" # See the terminate() function, where this script exits. exit "$ErrorCount"
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#2 | 31288 | Perforce maintenance |
Added another preflight check. Removed 'bc' from list of required utilities. Added new test for new prelfight check. |
||
#1 | 31284 | Perforce maintenance |
Added initial files. Nothing working yet; this is just the start of development. Files included: * rename_edge_serverid.sh - Script to rename an edge ServerID. * convert_edge_to_commit.sh - Script to convert a filtered edge into a commit. * cli_test.cfg - Test Suite (using SDP run_cli_tests.sh script). * r - Test suite wrapper. * .p4ignore - Just a typical P4IGNORE file. |