system-config/testinfra/test_borg_backups.py
Clark Boylan 962af3e24e Use newer borgbackup on Ubuntu Noble
Python3.12 (which is the python version on Noble) is not compatible with
our pinned borgbackup version (1.1.18). We get his errors when building
borgbackup on python3.12:

  ‘PyLongObject’ {aka ‘struct _longobject’} has no member named ‘ob_digit’

We update to 1.2.8 on Noble which is one of the oldest versions claiming
python3.12 support. We try to use the oldest version to ensure maximum
compatiblity with 1.1.18 on the backup servers. Our CI job should give
us decent coverage and then the new paste02 will be the production
canary for whether or not these versions are compatible enough with each
other. No other servers should be effected in the initial pass.

Note there is an upgrade event horizon for using repos with borg<1.2.5
and borg >=1.2.5. It only affects repos that have archives that lack
TAMs. My read on it is that newer borg can treat those archives as
invalid and unceremoniously delete them. This is a problem if they are
valid archives and don't have a TAM. I suspect we will avoid this
problem because borg >= 1.0.9 creates TAMs with archives and we prune
our archives so older ones should be long gone. More info on this can be
found in these documents and reviewers are encouraged to read them:

  https://borgbackup.readthedocs.io/en/1.2-maint/changes.html#pre-1-2-5-archives-spoofing-vulnerability-cve-2023-36811
  https://borgbackup.readthedocs.io/en/1.2-maint/changes.html#borg-1-1-x-to-1-2-x
  https://borgbackup.readthedocs.io/en/stable/usage/upgrade.html#borg-1-x-y-upgrades

I have left a todo for us to upgrade all of the services to 1.2.8 that
can run it (it requires python3.8 or newer so Focal or newer) but for
now we are taking baby steps.

Change-Id: I0c5ca758149b85aeec5321a704300489a57a3cc1
2025-01-17 07:54:54 -08:00

141 lines
5.2 KiB
Python

# Copyright 2019 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os.path
import pytest
testinfra_hosts = ['borg-backup01.region.provider.opendev.org',
'borg-backup-bionic.opendev.org',
'borg-backup-focal.opendev.org',
'borg-backup-jammy.opendev.org',
'borg-backup-noble.opendev.org']
def test_borg_installed(host):
f = host.file('/opt/borg/bin/borg')
assert f.exists
cmd = host.run('/opt/borg/bin/borg --version')
assert cmd.succeeded
# NOTE(ianw): deliberately pinned; we want to be careful if we
# update that the new version is compatible with old repos.
hostname = host.backend.get_hostname()
if hostname == 'borg-backup-noble.opendev.org':
assert '1.2.8' in cmd.stdout
else:
assert '1.1.18' in cmd.stdout
def test_borg_server_users(host):
hostname = host.backend.get_hostname()
if hostname != 'borg-backup01.region.provider.opendev.org':
pytest.skip()
for username in ('borg-borg-backup-bionic',
'borg-borg-backup-focal',
'borg-borg-backup-jammy',
'borg-borg-backup-noble'):
homedir = os.path.join('/opt/backups/', username)
borg_repo = os.path.join(homedir, 'backup')
authorized_keys = os.path.join(homedir, '.ssh', 'authorized_keys')
user = host.user(username)
assert user.exists
assert user.home == homedir
f = host.file(authorized_keys)
assert f.exists
assert f.contains("ssh-ed25519")
f = host.file(borg_repo)
assert f.exists
# test retired stamp is made for host in retired group
f = host.file('/opt/backups/borg-retired/.retired')
assert f.exists
# test purge for host in purge group
f = host.file('/opt/backups/borg-purge/backup')
assert not f.exists
def test_borg_backup_host_config(host):
hostname = host.backend.get_hostname()
if hostname == 'borg-backup01.region.provider.opendev.org':
pytest.skip()
f = host.file('/usr/local/bin/borg-backup')
assert f.exists
f = host.file('/root/.ssh/id_borg_backup_ed25519')
assert f.exists
f = host.file('/root/.ssh/config')
assert f.exists
assert f.contains('Host borg-backup01.region.provider.opendev.org')
def test_borg_backup(host):
hostname = host.backend.get_hostname()
if hostname == 'borg-backup01.region.provider.opendev.org':
pytest.skip()
# Note that newer borg (>=1.2) will exit non zero for warnings. This
# is expected and we try to mitigate common problems like files changing
# while backed up by excluding the log file we write to in the command
# from the backups.
# https://borgbackup.readthedocs.io/en/1.2-maint/usage/general.html#return-codes
cmd = host.run(
'/usr/local/bin/borg-backup borg-backup01.region.provider.opendev.org 2>> '
'/var/log/borg-backup-borg-backup01.region.provider.opendev.org.log')
assert cmd.succeeded
cmd = host.run(
'/usr/local/bin/borg-mount borg-backup01.region.provider.opendev.org')
assert cmd.succeeded
cmd = host.run('ls /opt/backups')
# this directory should now have a directory
# borg-backup-<distro>-YYYY-MM-DDT...
assert hostname.split('.')[0] in cmd.stdout
# unmount it for sanity
cmd = host.run('umount /opt/backups')
assert cmd.succeeded
def test_borg_server_prune(host):
hostname = host.backend.get_hostname()
if hostname != 'borg-backup01.region.provider.opendev.org':
pytest.skip()
# bit of a hack; instead of making a host, backing it up, and then
# retiring it -- which would require testing multiple runs of the
# backup process -- simulate the retired user being active by just
# making a small archive. This ensure the prune script works on
# user flagged as retired.
cmd = host.run('dd if=/dev/urandom of=/tmp/borg-backup.random bs=1M count=5')
assert cmd.succeeded
cmd = host.run('sudo -u borg-retired /opt/borg/bin/borg init --encryption=none /opt/backups/borg-retired/backup')
assert cmd.succeeded
cmd = host.run('sudo -u borg-retired /opt/borg/bin/borg create /opt/backups/borg-retired/backup::test-9999-12-12T00:00:00 /tmp/borg-backup.random')
assert cmd.succeeded
cmd = host.run('echo "prune" | NO_LOG_FILE=1 /usr/local/bin/prune-borg-backups &> /var/log/prune-borg-backups.log')
assert cmd.succeeded
def test_borg_server_verify(host):
hostname = host.backend.get_hostname()
if hostname != 'borg-backup01.region.provider.opendev.org':
pytest.skip()
cmd = host.run('/usr/local/bin/verify-borg-backups &> /var/log/verify-borg-backups.log')
assert cmd.succeeded