Skip to content

Commit 7615903

Browse files
authored
Merge branch 'master' into djdefi-perf-faq
2 parents 50406e6 + c28e9eb commit 7615903

File tree

9 files changed

+262
-29
lines changed

9 files changed

+262
-29
lines changed

.github/workflows/main.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ jobs:
2121
- name: Install Dependencies (macOS)
2222
run: |
2323
brew install gnu-tar shellcheck jq pigz coreutils gnu-sed gnu-getopt
24-
brew unlink parallel
2524
brew install moreutils gawk
2625
if: matrix.os == 'macos-latest'
2726
- name: Get Sources

bin/ghe-host-check

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,38 @@ options="
5252
port=$(ssh_port_part "$host")
5353
hostname=$(ssh_host_part "$host")
5454

55+
set +e
5556
# ghe-negotiate-version verifies if the target is a Github Enterprise Server instance
5657
output=$(echo "ghe-negotiate-version backup-utils $BACKUP_UTILS_VERSION" | ghe-ssh -o BatchMode=no $options $host -- /bin/sh 2>&1)
58+
rc=$?
59+
set -e
60+
61+
if [ $rc -ne 0 ]; then
62+
case $rc in
63+
255)
64+
if echo "$output" | grep -i "port 22: Network is unreachable\|port 22: connection refused\|port 22: no route to host\|ssh_exchange_identification: Connection closed by remote host\|Connection timed out during banner exchange\|port 22: Connection timed out" >/dev/null; then
65+
exec "$(basename $0)" "$hostname:122"
66+
fi
67+
68+
echo "$output" 1>&2
69+
echo "Error: ssh connection with '$host' failed" 1>&2
70+
echo "Note that your SSH key needs to be setup on $host as described in:" 1>&2
71+
echo "* https://enterprise.github.com/help/articles/adding-an-ssh-key-for-shell-access" 1>&2
72+
;;
73+
101)
74+
echo "Error: couldn't read GitHub Enterprise Server fingerprint on '$host' or this isn't a GitHub appliance." 1>&2
75+
;;
76+
1)
77+
if [ "${port:-22}" -eq 22 ] && echo "$output" | grep "use port 122" >/dev/null; then
78+
exec "$(basename $0)" "$hostname:122"
79+
else
80+
echo "$output" 1>&2
81+
fi
82+
;;
83+
84+
esac
85+
exit $rc
86+
fi
5787

5888
CLUSTER=false
5989
if ghe-ssh "$host" -- \

docs/requirements.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ be running GitHub Enterprise Server 2.12.x or 2.13.x. You can't restore a snapsh
6363

6464
**Note**: You _cannot_ restore a backup created from a newer verison of GitHub Enterprise Server to an older version. For example, an attempt to restore a snapshot of GitHub Enterprise Server 2.21 to a GitHub Enterprise Server 2.20 environment will fail with an error of `Error: Snapshot can not be restored to an older release of GitHub Enterprise Server.`.
6565

66+
## Multiple backup hosts
67+
68+
Using multiple backup hosts or backup configurations is not currently recommended.
69+
70+
Due to how some components of Backup Utiltiies (e.g. MSSQL) take incremental backups, running another instance of Backup Utilities may result in unrestorable snapshots as data may be split across backup hosts. If you still wish to have multiple instances of Backup Utilties for redundancy purposes or to run at different frequencies, ensure that they share the same `GHE_DATA_DIR` backup directory.
71+
6672
[1]: https://www.gnu.org/software/bash/
6773
[2]: https://git-scm.com/
6874
[3]: https://www.openssh.com/

share/github-backup-utils/ghe-backup-mssql

Lines changed: 152 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@ set -e
1414
backup_dir="$GHE_SNAPSHOT_DIR/mssql"
1515
last_mssql=
1616
backup_command=
17-
take_full=
18-
take_diff=
17+
backup_type=
1918
full_expire=
2019
diff_expire=
2120
tran_expire=
@@ -53,23 +52,23 @@ find_timestamp() {
5352
echo $datetime_part
5453
}
5554

56-
ensure_same_dbs() {
55+
actions_dbs() {
5756
all_dbs=$(echo 'set -o pipefail; ghe-mssql-console -y -n -q "SET NOCOUNT ON; SELECT name FROM sys.databases"' | ghe-ssh "$GHE_HOSTNAME" /bin/bash)
58-
59-
remotes=()
6057
for db in $all_dbs; do
6158
if [[ ! "$db" =~ ^(master|tempdb|model|msdb)$ ]] && [[ "$db" =~ ^[a-zA-Z0-9_-]+$ ]]; then
62-
remotes+=("$db")
59+
echo "$db"
6360
fi
6461
done
62+
}
6563

64+
ensure_same_dbs() {
6665
locals=()
6766
while read -r file; do
6867
filename=$(basename "$file")
6968
locals+=("$filename")
7069
done < <(find "$1" \( -name "*.bak" -o -name "*.diff" -o -name "*.log" \))
7170

72-
for remote in "${remotes[@]}"; do
71+
for remote in $(actions_dbs); do
7372
remaining_locals=()
7473
for local in "${locals[@]}"; do
7574
if ! [[ "$local" == "$remote"* ]]; then
@@ -90,13 +89,67 @@ ensure_same_dbs() {
9089
fi
9190
}
9291

92+
run_query() {
93+
echo "set -o pipefail; ghe-mssql-console -y -n -q \"SET NOCOUNT ON; $1\"" | ghe-ssh "$GHE_HOSTNAME" /bin/bash | sed 's/^[[:space:]]*//;s/[[:space:]]*$//'
94+
}
95+
96+
get_latest_backup_file() {
97+
backups_dir=$1
98+
db=$2
99+
ext=$3
100+
101+
latest_full_backup=$(find "$backups_dir" -type f -name "$db*.$ext" | egrep '[0-9]{8}T[0-9]{6}' | sort | tail -n 1)
102+
latest_full_backup_file="${latest_full_backup##*/}"
103+
echo "$latest_full_backup_file"
104+
}
105+
106+
get_backup_val() {
107+
db=$1
108+
filename=$2
109+
column=$3
110+
run_query "
111+
SELECT s.$column
112+
FROM msdb.dbo.backupset s
113+
JOIN msdb.dbo.backupmediafamily f
114+
ON s.media_set_id = f.media_set_id
115+
WHERE s.database_name = '$db' AND f.physical_device_name LIKE '%$filename'"
116+
}
117+
118+
get_backup_checkpoint_lsn() {
119+
get_backup_val "$1" "$2" "checkpoint_lsn"
120+
}
121+
122+
get_backup_last_lsn() {
123+
get_backup_val "$1" "$2" "last_lsn"
124+
}
125+
126+
get_next_log_backup_starting_lsn() {
127+
db=$1
128+
# last_log_backup_lsn: The starting log sequence number of the next log backup
129+
# https://docs.microsoft.com/en-us/sql/relational-databases/system-catalog-views/sys-database-recovery-status-transact-sql
130+
run_query "
131+
SELECT last_log_backup_lsn
132+
FROM sys.database_recovery_status drs
133+
JOIN sys.databases db on drs.database_id = db.database_id
134+
WHERE db.name = '$db'"
135+
}
136+
137+
get_next_diff_backup_base_lsn() {
138+
db=$1
139+
# differential_base_lsn: Base for differential backups. Data extents changed after this LSN will be included in a differential backup.
140+
# https://docs.microsoft.com/en-us/sql/relational-databases/system-catalog-views/sys-master-files-transact-sql
141+
run_query "
142+
SELECT differential_base_lsn
143+
FROM sys.master_files mf
144+
WHERE mf.name = '$db'"
145+
}
146+
93147
last_mssql=$GHE_DATA_DIR/current/mssql
94148

95149
if [ ! -d $last_mssql ] \
96150
|| [ -z "$(find $last_mssql -type f -name '*.bak' | head -n 1)" ]; then
97151
ghe_verbose "Taking first full backup"
98-
take_full=1
99-
backup_command='ghe-export-mssql'
152+
backup_type="full"
100153
else
101154
ensure_same_dbs "$last_mssql"
102155

@@ -128,25 +181,89 @@ else
128181
ghe_verbose "current $current, full expire $full_expire, \
129182
diff expire $diff_expire, tran expire $tran_expire"
130183

131-
# Determine the type of backup to take
184+
# Determine the type of backup to take based on expiry time
132185
if [ $current -gt $full_expire ]; then
133-
ghe_verbose "Taking full backup"
134-
take_full=1
135-
backup_command='ghe-export-mssql'
186+
backup_type='full'
136187
elif [ $current -gt $diff_expire ]; then
137-
ghe_verbose "Taking diff backup"
138-
take_diff=1
139-
backup_command='ghe-export-mssql -d'
188+
backup_type='diff'
140189
elif [ $current -gt $tran_expire ]; then
141-
ghe_verbose "Taking transaction backup"
142-
backup_command='ghe-export-mssql -t'
190+
backup_type='transaction'
143191
fi
192+
193+
# Upgrade to a full backup if the diff/transaction backup might not be restorable due to other backup mechanisms interfering
194+
# with the transaction LSN chain or differential base LSN.
195+
if [ "$backup_type" == 'diff' ] || [ "$backup_type" == 'transaction' ]; then
196+
ghe_verbose "Checking for conflicting backups to ensure a $backup_type backup is sufficient"
197+
198+
for db in $(actions_dbs); do
199+
# Ensure that a diff backup will be based on the full backup file we have (rather than one another backup mechanism took)
200+
if [ "$backup_type" == 'diff' ]; then
201+
full_backup_file=$(get_latest_backup_file "$last_mssql" "$db" "bak")
202+
if [[ "$full_backup_file" == "" ]]; then
203+
ghe_verbose "Taking a full backup instead of a diff backup because for $db a full backup file wasn't found"
204+
backup_type="full"
205+
break
206+
fi
207+
208+
full_backup_file_checkpoint_lsn=$(get_backup_checkpoint_lsn "$db" "$full_backup_file")
209+
if [[ "$full_backup_file_checkpoint_lsn" = "NULL" ]] || [[ "$full_backup_file_checkpoint_lsn" == "" ]]; then
210+
ghe_verbose "Taking a full backup instead of a diff backup because for $db the checkpoint LSN for $full_backup_file couldn't be determined"
211+
backup_type="full"
212+
break
213+
fi
214+
215+
next_diff_backup_base_lsn=$(get_next_diff_backup_base_lsn "$db")
216+
if [[ "$next_diff_backup_base_lsn" = "NULL" ]] || [[ "$next_diff_backup_base_lsn" == "" ]]; then
217+
ghe_verbose "Taking a full backup instead of a $backup_type backup because for $db the base LSN for the next diff backup couldn't be determined"
218+
backup_type="full"
219+
break
220+
fi
221+
222+
# The base of the diff backup we're about to take must exactly match the checkpoint LSN of the full backup file we have
223+
if [[ "$next_diff_backup_base_lsn" -ne "$full_backup_file_checkpoint_lsn" ]]; then
224+
ghe_verbose "Taking a full backup instead of a $backup_type backup because for $db the diff would have base LSN $next_diff_backup_base_lsn yet our full backup has checkpoint LSN $full_backup_file_checkpoint_lsn"
225+
backup_type="full"
226+
break
227+
fi
228+
fi
229+
230+
# Ensure that a transaction log backup will immediately follow the previous one
231+
latest_log_backup_file=$(get_latest_backup_file "$last_mssql" "$db" "log")
232+
if [[ "$latest_log_backup_file" == "" ]]; then
233+
ghe_verbose "Taking a full backup instead of a $backup_type backup because for $db a previous transaction log backup wasn't found"
234+
backup_type="full"
235+
break
236+
fi
237+
238+
latest_log_backup_last_lsn=$(get_backup_last_lsn "$db" "$latest_log_backup_file")
239+
if [[ "$latest_log_backup_last_lsn" = "NULL" ]] || [[ "$latest_log_backup_last_lsn" == "" ]]; then
240+
ghe_verbose "Taking a full backup instead of a $backup_type backup because for $db the LSN range for $latest_log_backup_file couldn't be determined"
241+
backup_type="full"
242+
break
243+
fi
244+
245+
next_log_backup_starting_lsn=$(get_next_log_backup_starting_lsn "$db")
246+
if [[ "$next_log_backup_starting_lsn" = "NULL" ]] || [[ "$next_log_backup_starting_lsn" == "" ]]; then
247+
ghe_verbose "Taking a full backup instead of a $backup_type backup because for $db the starting LSN for the next log backup couldn't be determined"
248+
backup_type="full"
249+
break
250+
fi
251+
252+
# The starting LSN of the backup we're about to take must be equal to (or before) the last LSN from the last backup,
253+
# otherwise there'll be a gap and the logfiles won't be restorable
254+
if [[ "$next_log_backup_starting_lsn" -gt "$latest_log_backup_last_lsn" ]]; then
255+
ghe_verbose "Taking a full backup instead of a $backup_type backup because for $db a gap would exist between the last backup ending at LSN $latest_log_backup_last_lsn and next backup starting at $next_log_backup_starting_lsn"
256+
backup_type="full"
257+
break
258+
fi
259+
done
260+
fi
144261
fi
145262

146263
# Make sure root backup dir exists if this is the first run
147264
mkdir -p "$backup_dir"
148265

149-
# Create hard links to save disk space and time
266+
# Use hard links to "copy" over previous applicable backups to the new snapshot folder to save disk space and time
150267
if [ -d $last_mssql ]; then
151268
for p in $last_mssql/*
152269
do
@@ -156,15 +273,18 @@ if [ -d $last_mssql ]; then
156273
extension="${filename##*.}"
157274
transfer=
158275

159-
if [ $extension = "bak" ] && [ -z $take_full ]; then
276+
# Copy full backups unless we're taking a new full backup
277+
if [ $extension = "bak" ] && [ "$backup_type" != 'full' ]; then
160278
transfer=1
161279
fi
162280

163-
if [ $extension = "diff" ] && [ -z $take_full ] && [ -z $take_diff ]; then
281+
# Copy diff backups unless we're taking a new full or diff backup
282+
if [ $extension = "diff" ] && [ "$backup_type" != 'full' ] && [ "$backup_type" != 'diff' ]; then
164283
transfer=1
165284
fi
166285

167-
if [ $extension = "log" ] && [ -z $take_full ] && [ -z $take_diff ]; then
286+
# Copy transaction log backups unless we're taking a new full or diff backup
287+
if [ $extension = "log" ] && [ "$backup_type" != 'full' ] && [ "$backup_type" != 'diff' ]; then
168288
transfer=1
169289
fi
170290

@@ -175,7 +295,16 @@ if [ -d $last_mssql ]; then
175295
done
176296
fi
177297

178-
if [ -n "$backup_command" ]; then
298+
if [ -n "$backup_type" ]; then
299+
ghe_verbose "Taking $backup_type backup"
300+
301+
backup_command='ghe-export-mssql'
302+
if [ "$backup_type" = "diff" ]; then
303+
backup_command='ghe-export-mssql -d'
304+
elif [ "$backup_type" = "transaction" ]; then
305+
backup_command='ghe-export-mssql -t'
306+
fi
307+
179308
bm_start "$(basename $0)"
180309
ghe-ssh "$GHE_HOSTNAME" -- "$backup_command" || failures="$failures mssql"
181310
bm_end "$(basename $0)"

test/bin/ghe-mssql-console

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,21 @@
11
#!/usr/bin/env bash
2-
# Returns environment variable REMOTE_DBS, the variable should be set as space-delimited string
32
# Tests use this to emulate ghe-mssql-console from a remote GitHub Enterprise Server
4-
echo $REMOTE_DBS
3+
if [[ "$*" == *"SELECT name FROM sys.databases"* ]]; then
4+
# REMOTE_DBS should be set as space-delimited string with the test database names
5+
echo "$REMOTE_DBS"
6+
elif [[ "$*" == *"last_log_backup_lsn"* ]]; then
7+
# Starting log sequence number of the next log backup.
8+
# Should match the most recent log backup last_lsn.
9+
echo "$NEXT_LOG_BACKUP_STARTING_LSN"
10+
elif [[ "$*" == *"last_lsn"* ]]; then
11+
# Last LSN of the transaction log backup file being looked up.
12+
echo "$LOG_BACKUP_FILE_LAST_LSN"
13+
elif [[ "$*" == *"differential_base_lsn"* ]]; then
14+
# Next diff backup base LSN. Should match full checkpoint LSN.
15+
echo "$DIFFERENTIAL_BASE_LSN"
16+
elif [[ "$*" == *"checkpoint_lsn"* ]]; then
17+
# Checkpoint LSN of the full backup file being looked up.
18+
echo "$FULL_BACKUP_FILE_LSN"
19+
else
20+
echo "UNKNOWN QUERY: ghe-mssql-console test stub failed on: $*"
21+
fi

test/bin/systemctl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ghe-fake-true

0 commit comments

Comments
 (0)