Skip to content
This repository was archived by the owner on Jun 15, 2025. It is now read-only.

Commit 036ea8b

Browse files
committed
Add remote file repository test case
This change adds a test case to execute the code paths taken when backing up local data to a remote file repository. The connection to the remote host is established using SSH. Because we cannot always assume that the user has an SSH host available this test is dependent on the setting of the TEST_SSH_HOST (containing the host name as understood by ssh(1)) environment variable and, thus, optional. The test itself creates a temporary directory on the remote host that acts as the file repository's root. It then performs a full backup and restore cycle using this repository.
1 parent 7ebc28a commit 036ea8b

File tree

1 file changed

+111
-25
lines changed

1 file changed

+111
-25
lines changed

btrfs-backup/src/deso/btrfs/test/testMain.py

Lines changed: 111 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
Mount,
4444
)
4545
from deso.btrfs.test.util import (
46+
mkdtemp,
4647
NamedTemporaryFile,
4748
TemporaryDirectory,
4849
)
@@ -54,6 +55,10 @@
5455
findCommand,
5556
pipeline,
5657
)
58+
from os import (
59+
environ,
60+
rmdir,
61+
)
5762
from os.path import (
5863
extsep,
5964
join,
@@ -362,38 +367,22 @@ def testNoStderrRead(self):
362367
btrfsMain([argv[0]] + args.split())
363368

364369

365-
class TestMainRun(BtrfsTestCase):
366-
"""Test case invoking the btrfs-progam for end-to-end tests."""
370+
class TestMainRunBase(BtrfsTestCase):
371+
"""Test case base class for btrfs-backup end-to-end tests."""
367372
def setUp(self):
368-
"""Create the test harness with two btrfs volumes."""
373+
"""Create the test harness with a single btrfs volume and some data."""
369374
with defer() as d:
370375
super().setUp()
371376
d.defer(super().tearDown)
372377

373-
self._bdevice = BtrfsDevice()
374-
d.defer(self._bdevice.destroy)
375-
376-
self._backup = Mount(self._bdevice.device())
377-
d.defer(self._backup.destroy)
378-
379-
with alias(self._mount) as m,\
380-
alias(self._backup) as b:
378+
with alias(self._mount) as m:
381379
self._user = make(m, "home", "user", subvol=True)
382380
self._root = make(m, "root", subvol=True)
383381
self._snapshots = make(m, "snapshots")
384-
self._backups = make(b, "backup")
385382

386383
d.release()
387384

388385

389-
def tearDown(self):
390-
"""Unmount the backup device and destroy it."""
391-
self._backup.destroy()
392-
self._bdevice.destroy()
393-
394-
super().tearDown()
395-
396-
397386
def wipeSubvolumes(self, path, pattern="*"):
398387
"""Remove all subvolumes in a given path (non recursively)."""
399388
snapshots = glob(join(path, pattern))
@@ -428,15 +417,14 @@ def restore(self, src, dst, *options, **kwargs):
428417

429418
def performTest(self, backup, restore, src, dst):
430419
"""Test a simple run of the program with two subvolumes."""
431-
with alias(self._mount) as m,\
432-
alias(self._backup) as b:
420+
with alias(self._mount) as m:
433421
make(m, "home", "user", "data", "movie.mp4", data=b"abcdefgh")
434422
make(m, "root", ".ssh", "key.pub", data=b"1234567890")
435423

436424
# Case 1) Run in ordinary fashion to backup data into a
437-
# separate btrfs backup volume.
425+
# separate btrfs backup volume. Result is verified
426+
# by the restore operation later on.
438427
backup(src, dst)
439-
self.assertEqual(len(glob(b.path("backup", "*"))), 2)
440428

441429
# Case 2) Delete all created snapshots (really only the
442430
# snapshots for now) from our "source" and try
@@ -477,6 +465,32 @@ def performTest(self, backup, restore, src, dst):
477465
self.assertContains(m.path(root, ".ssh", "key.pub"), "1234567890")
478466

479467

468+
class TestLocalMainRun(TestMainRunBase):
469+
"""Test case invoking the btrfs-progam for end-to-end tests with a local backup repository."""
470+
def setUp(self):
471+
"""Create a btrfs device for the backups."""
472+
with defer() as d:
473+
super().setUp()
474+
d.defer(super().tearDown)
475+
476+
self._bdevice = BtrfsDevice()
477+
d.defer(self._bdevice.destroy)
478+
479+
self._backup = Mount(self._bdevice.device())
480+
d.defer(self._backup.destroy)
481+
482+
self._backups = make(self._backup, "backup")
483+
d.release()
484+
485+
486+
def tearDown(self):
487+
"""Unmount the backup device and destroy it."""
488+
self._backup.destroy()
489+
self._bdevice.destroy()
490+
491+
super().tearDown()
492+
493+
480494
def testNormalRun(self):
481495
"""Test backup and restore."""
482496
self.performTest(self.backup, self.restore, self._snapshots, self._backups)
@@ -514,7 +528,6 @@ def restore(src, dst, *options, reverse=False):
514528
"--join",
515529
]
516530
self.restore(src, dst, *options, reverse=reverse)
517-
518531
try:
519532
GPG = findCommand("gpg")
520533
except FileNotFoundError:
@@ -536,5 +549,78 @@ def restore(src, dst, *options, reverse=False):
536549
self.performTest(backup, restore, self._snapshots, self._backups)
537550

538551

552+
class TestRemoteMainRun(TestMainRunBase):
553+
"""Test case invoking the btrfs-progam for end-to-end tests with a remote backup repository."""
554+
def setUp(self):
555+
"""Determine a directory to use on the remote host."""
556+
def tmpdirpath():
557+
"""Get the path to a temporary directory that does not exist.
558+
559+
Note: tempfile.mktemp provides a superset of the functionality
560+
this function provides but got deprecated.
561+
"""
562+
d = mkdtemp()
563+
rmdir(d)
564+
return d
565+
566+
with defer() as d:
567+
super().setUp()
568+
d.defer(super().tearDown)
569+
570+
self._backups = join(tmpdirpath(), "backup")
571+
d.release()
572+
573+
574+
# TODO: We still require a test that backs up data to a native remote
575+
# repository. However, that requires a btrfs file system to
576+
# exist at a particular location on the remote host which is not
577+
# so trivial to have.
578+
579+
580+
def testSshRun(self):
581+
"""Test backup and restore over an SSH connection to a remote host."""
582+
def backup(*options):
583+
"""Invoke the program to backup snapshots/subvolumes to a remote host."""
584+
options = list(options)
585+
options += [
586+
"--remote-cmd=/usr/bin/ssh %s" % host,
587+
"--snapshot-ext=bin",
588+
"--recv-filter=/bin/dd of={file}",
589+
"--no-read-stderr",
590+
]
591+
self.backup(*options)
592+
593+
def restore(src, dst, *options, reverse=False):
594+
"""Invoke the program to restore snapshots/subvolumes from a remote host."""
595+
filt = "recv" if reverse else "send"
596+
options = list(options)
597+
options += [
598+
"--remote-cmd=/usr/bin/ssh %s" % host,
599+
"--snapshot-ext=bin",
600+
"--%s-filter=/bin/dd if={file}" % filt,
601+
"--no-read-stderr",
602+
]
603+
self.restore(src, dst, *options, reverse=reverse)
604+
605+
try:
606+
SSH = findCommand("ssh")
607+
except FileNotFoundError:
608+
raise SkipTest("SSH not found")
609+
610+
try:
611+
host = environ["TEST_SSH_HOST"]
612+
except KeyError:
613+
raise SkipTest("TEST_SSH_HOST environment variable not set")
614+
615+
with defer() as d:
616+
# This part is a bit tricky. We do not know the directory
617+
# structure on the remote host. We only have a path that is unique
618+
# on our local machine. We try to create it on the remote host to
619+
# work on it later on.
620+
execute(SSH, host, "mkdir -p %s" % self._backups, stderr=None)
621+
d.defer(lambda: execute(SSH, host, "rm -r %s" % self._backups, stderr=None))
622+
self.performTest(backup, restore, self._snapshots, self._backups)
623+
624+
539625
if __name__ == "__main__":
540626
main()

0 commit comments

Comments
 (0)