Browse Source

Merge pull request #8755 from ThomasWaldmann/fix-remote-modern-exit-codes-1.4

fix remote repository exception handling / modern exit codes, fixes #8631
TW 1 month ago
parent
commit
dd98328c17
3 changed files with 37 additions and 13 deletions
  1. 1 1
      .github/workflows/ci.yml
  2. 25 12
      src/borg/remote.py
  3. 11 0
      src/borg/testsuite/archiver.py

+ 1 - 1
.github/workflows/ci.yml

@@ -47,7 +47,7 @@ jobs:
       fail-fast: false
       matrix:
         include:
-            - os: ubuntu-20.04
+            - os: ubuntu-22.04
               python-version: '3.9'
               toxenv: py39-fuse2
             - os: ubuntu-22.04

+ 25 - 12
src/borg/remote.py

@@ -251,6 +251,19 @@ class RepositoryServer:  # pragma: no cover
                         args = self.filter_args(f, args)
                         res = f(**args)
                     except BaseException as e:
+                        # These exceptions are reconstructed on the client end in RemoteRepository.call_many(),
+                        # and will be handled just like locally raised exceptions. Suppress the remote traceback
+                        # for these, except ErrorWithTraceback, which should always display a traceback.
+                        reconstructed_exceptions = (
+                            Repository.InvalidRepository,
+                            Repository.InvalidRepositoryConfig,
+                            Repository.DoesNotExist,
+                            Repository.AlreadyExists,
+                            Repository.PathAlreadyExists,
+                            PathNotAllowed,
+                            Repository.InsufficientFreeSpaceError,
+                            Repository.StorageQuotaExceeded,
+                        )
                         if dictFormat:
                             ex_short = traceback.format_exception_only(e.__class__, e)
                             ex_full = traceback.format_exception(*sys.exc_info())
@@ -258,12 +271,7 @@ class RepositoryServer:  # pragma: no cover
                             if isinstance(e, Error):
                                 ex_short = [e.get_message()]
                                 ex_trace = e.traceback
-                            if isinstance(e, (Repository.DoesNotExist, Repository.AlreadyExists, PathNotAllowed)):
-                                # These exceptions are reconstructed on the client end in RemoteRepository.call_many(),
-                                # and will be handled just like locally raised exceptions. Suppress the remote traceback
-                                # for these, except ErrorWithTraceback, which should always display a traceback.
-                                pass
-                            else:
+                            if not isinstance(e, reconstructed_exceptions):
                                 logging.debug('\n'.join(ex_full))
 
                             try:
@@ -286,12 +294,7 @@ class RepositoryServer:  # pragma: no cover
 
                             os_write(stdout_fd, msg)
                         else:
-                            if isinstance(e, (Repository.DoesNotExist, Repository.AlreadyExists, PathNotAllowed)):
-                                # These exceptions are reconstructed on the client end in RemoteRepository.call_many(),
-                                # and will be handled just like locally raised exceptions. Suppress the remote traceback
-                                # for these, except ErrorWithTraceback, which should always display a traceback.
-                                pass
-                            else:
+                            if not isinstance(e, reconstructed_exceptions):
                                 if isinstance(e, Error):
                                     tb_log_level = logging.ERROR if e.traceback else logging.DEBUG
                                     msg = e.get_message()
@@ -759,6 +762,8 @@ This problem will go away as soon as the server has been upgraded to 1.0.7+.
                 raise Error(args[0].decode())
             elif error == 'ErrorWithTraceback':
                 raise ErrorWithTraceback(args[0].decode())
+            elif error == 'InvalidRepository':
+                raise Repository.InvalidRepository(self.location.processed)
             elif error == 'DoesNotExist':
                 raise Repository.DoesNotExist(self.location.processed)
             elif error == 'AlreadyExists':
@@ -782,6 +787,8 @@ This problem will go away as soon as the server has been upgraded to 1.0.7+.
                     raise PathNotAllowed(args[0].decode())
             elif error == 'PathPermissionDenied':
                 raise Repository.PathPermissionDenied(args[0].decode())
+            elif error == 'PathAlreadyExists':
+                raise Repository.PathAlreadyExists(args[0].decode())
             elif error == 'ParentPathDoesNotExist':
                 raise Repository.ParentPathDoesNotExist(args[0].decode())
             elif error == 'ObjectNotFound':
@@ -814,6 +821,12 @@ This problem will go away as soon as the server has been upgraded to 1.0.7+.
                     raise NotMyLock('(not available)')
                 else:
                     raise NotMyLock(args[0].decode())
+            elif error == 'InsufficientFreeSpaceError':
+                raise Repository.InsufficientFreeSpaceError(args[0].decode(), args[1].decode())
+            elif error == 'InvalidRepositoryConfig':
+                raise Repository.InvalidRepositoryConfig(self.location.processed, args[1].decode())
+            elif error == 'StorageQuotaExceeded':
+                raise Repository.StorageQuotaExceeded(args[0].decode(), args[1].decode())
             else:
                 raise self.RPCError(unpacked)
 

+ 11 - 0
src/borg/testsuite/archiver.py

@@ -3935,6 +3935,17 @@ id: 2 / e29442 3506da 4e1ea7 / 25f62a 5a3d41 - 02
         self.cmd('create', self.repository_location + '::test2', 'input')
         assert os.path.exists(nonce)
 
+    def test_exit_codes(self):
+        # we create the repo path, but do NOT initialize the borg repo,
+        # so the borg create commands are expected to fail with InvalidRepository.
+        os.makedirs(self.repository_path, exist_ok=True)
+        with environment_variable(BORG_EXIT_CODES='classic'):
+            self.cmd('create', self.repository_location + '::archive', 'input', fork=True,
+                     exit_code=EXIT_ERROR)
+        with environment_variable(BORG_EXIT_CODES='modern'):
+            self.cmd('create', self.repository_location + '::archive', 'input', fork=True,
+                     exit_code=Repository.InvalidRepository.exit_mcode)
+
 
 @unittest.skipUnless('binary' in BORG_EXES, 'no borg.exe available')
 class ArchiverTestCaseBinary(ArchiverTestCase):