mirror of
https://github.com/borgbackup/borg.git
synced 2026-10-12 02:11:36 +00:00
ssh://: explain a remote borg serve that fails to start
If the first request to the remote "borg serve" fails (e.g. a borg 1.x borg serve does not know --rest, or borg is not installed there), raise an Error showing the server's stderr and a hint (BORG_REMOTE_PATH, --from-borg1) instead of a "Local Exception" traceback. Repository.close() does not raise from closing the store while unwinding an error anymore (like the lock release before it): a dead store fails to close, too, which masked the original error. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
669b43980d
commit
93481e0fb8
+28
-12
@@ -1007,18 +1007,28 @@ class Repository:
|
||||
return f"<{self.__class__.__name__} {self._location}>"
|
||||
|
||||
def __enter__(self):
|
||||
if self.do_create:
|
||||
self.do_create = False
|
||||
self.create()
|
||||
self.created = True
|
||||
try:
|
||||
self.open(exclusive=bool(self.exclusive), lock_wait=self.lock_wait, lock=self.do_lock)
|
||||
except Exception:
|
||||
self.close(aborting=True)
|
||||
if self.created:
|
||||
# we just created the store, but could not open it: do not leave it behind (see create()).
|
||||
self.store.destroy()
|
||||
raise
|
||||
if self.do_create:
|
||||
self.do_create = False
|
||||
self.create()
|
||||
self.created = True
|
||||
try:
|
||||
self.open(exclusive=bool(self.exclusive), lock_wait=self.lock_wait, lock=self.do_lock)
|
||||
except Exception:
|
||||
self.close(aborting=True)
|
||||
if self.created:
|
||||
# we just created the store, but could not open it: do not leave it behind (see create()).
|
||||
self.store.destroy()
|
||||
raise
|
||||
except StoreBackendError as e:
|
||||
if self._location.proto != "ssh":
|
||||
raise
|
||||
# the first request to the remote "borg serve" failed, usually because it did not start.
|
||||
raise Error(
|
||||
f"Could not access the repository via borg serve on the remote host: {e}\n"
|
||||
"Is borg 2 installed there (see BORG_REMOTE_PATH)? "
|
||||
"For a borg 1.x repository, use --from-borg1."
|
||||
) from None
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
@@ -1387,7 +1397,13 @@ class Repository:
|
||||
logger.warning("failed to release the lock during close: %s", exc)
|
||||
self.lock = None
|
||||
if self.store_opened:
|
||||
self.store.close()
|
||||
try:
|
||||
self.store.close()
|
||||
except Exception as exc:
|
||||
if not unwinding:
|
||||
raise
|
||||
# when the store is dead (e.g. the remote borg serve exited), closing it fails, too.
|
||||
logger.debug("failed to close the store during close: %s", exc)
|
||||
self.store_opened = False
|
||||
self.opened = False
|
||||
self._pack_cache.clear()
|
||||
|
||||
@@ -8,6 +8,7 @@ from collections import namedtuple
|
||||
|
||||
import pytest
|
||||
from borghash import HashTableNT
|
||||
from borgstore.backends.rest import REST
|
||||
|
||||
from ..crypto.key import store_hash
|
||||
from .. import repository as repository_module
|
||||
@@ -15,7 +16,7 @@ from ..cache import chunkindex_is_invalid, delete_chunkindex_from_repo, write_ch
|
||||
from ..compress import CNONE
|
||||
from ..constants import MAX_CLOCK_SKEW, ROBJ_FILE_STREAM
|
||||
from ..crypto.key import AESOCBKey, AuthenticatedKey, Blake3AuthenticatedKey, CHPOKey
|
||||
from ..helpers import IntegrityError, Location, bin_to_hex
|
||||
from ..helpers import Error, IntegrityError, Location, bin_to_hex
|
||||
from ..hashindex import ChunkIndex, ChunkIndexEntry
|
||||
from ..repository import Repository, MAX_DATA_SIZE, MAX_VALIDATED_META_SIZE, propagate_rsh, rest_serve_command
|
||||
from ..repository import PackWriter, PackReader, PackTracker, superseded_gap_ranges
|
||||
@@ -97,6 +98,24 @@ def test_open_nonexistent_repository(tmp_path, proto):
|
||||
assert not os.path.exists(path)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("create", [False, True])
|
||||
def test_remote_serve_fails_to_start(tmp_path, monkeypatch, create):
|
||||
# e.g. a borg 1.x "borg serve" on the remote host does not know --rest: the user gets an error
|
||||
# showing the server's stderr and a hint, not a traceback of the dead store's close().
|
||||
def failing_rest_backend(location):
|
||||
error = "borg serve: error: unrecognized arguments: --rest"
|
||||
command = [sys.executable, "-c", f"import sys; sys.stderr.write({error!r} + '\\n'); sys.exit(2)"]
|
||||
# no waiting between the reconnect attempts, to keep the test fast.
|
||||
return REST(base_url="http://stdio-backend", command=command, reconnect_wait=0)
|
||||
|
||||
monkeypatch.setattr(repository_module, "build_rest_backend", failing_rest_backend)
|
||||
location = Location("ssh://__testsuite__/" + os.fspath(tmp_path / "repository"))
|
||||
with pytest.raises(Error, match="use --from-borg1") as exc_info:
|
||||
with Repository(location, exclusive=True, create=create):
|
||||
pass
|
||||
assert "unrecognized arguments: --rest" in str(exc_info.value)
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def repository(tmp_path):
|
||||
repository_location = os.fspath(tmp_path / "repository")
|
||||
|
||||
Reference in New Issue
Block a user