4343 Mount ,
4444)
4545from deso .btrfs .test .util import (
46+ mkdtemp ,
4647 NamedTemporaryFile ,
4748 TemporaryDirectory ,
4849)
5455 findCommand ,
5556 pipeline ,
5657)
58+ from os import (
59+ environ ,
60+ rmdir ,
61+ )
5762from 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+
539625if __name__ == "__main__" :
540626 main ()
0 commit comments