Browse Source

fix remote repository exception handling / exit codes, fixes #8631

also: reduce code duplication.
Thomas Waldmann 1 month ago
parent
commit
0b284db33a
2 changed files with 41 additions and 7 deletions
  1. 24 6
      src/borg/remote.py
  2. 17 1
      src/borg/testsuite/archiver/return_codes_test.py

+ 24 - 6
src/borg/remote.py

@@ -265,6 +265,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,
+                            )
                             # logger.exception(e)
                             ex_short = traceback.format_exception_only(e.__class__, e)
                             ex_full = traceback.format_exception(*sys.exc_info())
@@ -272,12 +285,7 @@ class RepositoryServer:  # pragma: no cover
                             if isinstance(e, Error):
                                 ex_short = [e.get_message()]
                                 ex_trace = e.traceback
-                            if isinstance(e, (self.RepoCls.DoesNotExist, self.RepoCls.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))
 
                             sys_info = sysinfo()
@@ -790,6 +798,8 @@ class RemoteRepository:
                 raise Error(args[0])
             elif error == "ErrorWithTraceback":
                 raise ErrorWithTraceback(args[0])
+            elif error == "InvalidRepository":
+                raise Repository.InvalidRepository(self.location.processed)
             elif error == "DoesNotExist":
                 raise Repository.DoesNotExist(self.location.processed)
             elif error == "AlreadyExists":
@@ -802,6 +812,8 @@ class RemoteRepository:
                 raise PathNotAllowed(args[0])
             elif error == "PathPermissionDenied":
                 raise Repository.PathPermissionDenied(args[0])
+            elif error == "PathAlreadyExists":
+                raise Repository.PathAlreadyExists(args[0])
             elif error == "ParentPathDoesNotExist":
                 raise Repository.ParentPathDoesNotExist(args[0])
             elif error == "ObjectNotFound":
@@ -818,6 +830,12 @@ class RemoteRepository:
                 raise NotMyLock(args[0])
             elif error == "NoManifestError":
                 raise NoManifestError
+            elif error == "InsufficientFreeSpaceError":
+                raise Repository.InsufficientFreeSpaceError(args[0], args[1])
+            elif error == "InvalidRepositoryConfig":
+                raise Repository.InvalidRepositoryConfig(self.location.processed, args[1])
+            elif error == "StorageQuotaExceeded":
+                raise Repository.StorageQuotaExceeded(args[0], args[1])
             else:
                 raise self.RPCError(unpacked)
 

+ 17 - 1
src/borg/testsuite/archiver/return_codes_test.py

@@ -1,6 +1,11 @@
+import os
+
 from ...constants import *  # NOQA
 from ...helpers import IncludePatternNeverMatchedWarning
-from . import cmd_fixture, changedir  # NOQA
+from ...repository import Repository
+from . import cmd, cmd_fixture, changedir, generate_archiver_tests  # NOQA
+
+pytest_generate_tests = lambda metafunc: generate_archiver_tests(metafunc, kinds="local,remote,binary")  # NOQA
 
 
 def test_return_codes(cmd_fixture, tmpdir):
@@ -17,3 +22,14 @@ def test_return_codes(cmd_fixture, tmpdir):
         assert rc == EXIT_SUCCESS
     rc, out = cmd_fixture("--repo=%s" % repo, "extract", "archive", "does/not/match")
     assert rc == IncludePatternNeverMatchedWarning().exit_code
+
+
+def test_exit_codes(archivers, request, tmpdir, monkeypatch):
+    archiver = request.getfixturevalue(archivers)
+    # we create the repo path, but do NOT initialize the borg repo,
+    # so the borg create commands are expected to fail with DoesNotExist (was: InvalidRepository in borg 1.4).
+    os.makedirs(archiver.repository_path)
+    monkeypatch.setenv("BORG_EXIT_CODES", "classic")
+    cmd(archiver, "create", "archive", "input", fork=True, exit_code=EXIT_ERROR)
+    monkeypatch.setenv("BORG_EXIT_CODES", "modern")
+    cmd(archiver, "create", "archive", "input", fork=True, exit_code=Repository.DoesNotExist.exit_mcode)