Переглянути джерело

Adding used storage quota to borg info (#7121)

Franco Ayala 2 роки тому
батько
коміт
ab465a75d4

+ 25 - 5
src/borg/archiver/rinfo_cmd.py

@@ -3,7 +3,7 @@ import textwrap
 
 from ._common import with_repository
 from ..constants import *  # NOQA
-from ..helpers import bin_to_hex, json_print, basic_json_data
+from ..helpers import bin_to_hex, json_print, basic_json_data, format_file_size
 from ..manifest import Manifest
 
 from ..logger import create_logger
@@ -30,7 +30,7 @@ class RInfoMixIn:
                 encryption += "\nKey file: %s" % key.find_key()
             info["encryption"] = encryption
 
-            print(
+            output = (
                 textwrap.dedent(
                     """
             Repository ID: {id}
@@ -38,8 +38,6 @@ class RInfoMixIn:
             Repository version: {version}
             Append only: {append_only}
             {encryption}
-            Cache: {cache.path}
-            Security dir: {security_dir}
             """
                 )
                 .strip()
@@ -48,9 +46,31 @@ class RInfoMixIn:
                     location=repository._location.canonical_path(),
                     version=repository.version,
                     append_only=repository.append_only,
-                    **info,
+                    encryption=info["encryption"],
                 )
             )
+
+            response = repository.info()
+            storage_quota = response["storage_quota"]
+            used = format_file_size(response["storage_quota_use"])
+
+            output += f"Storage quota: {used} used"
+            if storage_quota:
+                output += f" out of {format_file_size(storage_quota)}"
+            output += "\n"
+
+            output += (
+                textwrap.dedent(
+                    """
+                    Cache: {cache.path}
+                    Security dir: {security_dir}
+                    """
+                )
+                .strip()
+                .format(**info)
+            )
+
+            print(output)
             print(str(cache))
         return self.exit_code
 

+ 21 - 14
src/borg/repository.py

@@ -493,9 +493,22 @@ class Repository:
         self.id = unhexlify(self.config.get("repository", "id").strip())
         self.io = LoggedIO(self.path, self.max_segment_size, self.segments_per_dir)
 
+    def _load_hints(self):
+        if (transaction_id := self.get_transaction_id()) is None:
+            # self is a fresh repo, so transaction_id is None and there is no hints file
+            return
+        hints = self._unpack_hints(transaction_id)
+        self.version = hints["version"]
+        self.storage_quota_use = hints["storage_quota_use"]
+        self.shadow_index = hints["shadow_index"]
+
     def info(self):
         """return some infos about the repo (must be opened first)"""
-        return dict(id=self.id, version=self.version, append_only=self.append_only)
+        info = dict(id=self.id, version=self.version, append_only=self.append_only)
+        self._load_hints()
+        info["storage_quota"] = self.storage_quota
+        info["storage_quota_use"] = self.storage_quota_use
+        return info
 
     def close(self):
         if self.lock:
@@ -512,7 +525,6 @@ class Repository:
             self.rollback()
             raise exception
         self.check_free_space()
-        self.log_storage_quota()
         segment = self.io.write_commit()
         self.segments.setdefault(segment, 0)
         self.compact[segment] += LoggedIO.header_fmt.size
@@ -556,6 +568,12 @@ class Repository:
             self.commit(compact=False)
             return self.open_index(self.get_transaction_id())
 
+    def _unpack_hints(self, transaction_id):
+        hints_path = os.path.join(self.path, "hints.%d" % transaction_id)
+        integrity_data = self._read_integrity(transaction_id, "hints")
+        with IntegrityCheckedFile(hints_path, write=False, integrity_data=integrity_data) as fd:
+            return msgpack.unpack(fd)
+
     def prepare_txn(self, transaction_id, do_cleanup=True):
         self._active_txn = True
         if self.do_lock and not self.lock.got_exclusive_lock():
@@ -592,10 +610,8 @@ class Repository:
                 self.io.cleanup(transaction_id)
             hints_path = os.path.join(self.path, "hints.%d" % transaction_id)
             index_path = os.path.join(self.path, "index.%d" % transaction_id)
-            integrity_data = self._read_integrity(transaction_id, "hints")
             try:
-                with IntegrityCheckedFile(hints_path, write=False, integrity_data=integrity_data) as fd:
-                    hints = msgpack.unpack(fd)
+                hints = self._unpack_hints(transaction_id)
             except (msgpack.UnpackException, FileNotFoundError, FileIntegrityError) as e:
                 logger.warning("Repository hints file missing or corrupted, trying to recover: %s", e)
                 if not isinstance(e, FileNotFoundError):
@@ -622,7 +638,6 @@ class Repository:
                 self.compact = FreeSpace(hints["compact"])
                 self.storage_quota_use = hints.get("storage_quota_use", 0)
                 self.shadow_index = hints.get("shadow_index", {})
-            self.log_storage_quota()
             # Drop uncommitted segments in the shadow index
             for key, shadowed_segments in self.shadow_index.items():
                 for segment in list(shadowed_segments):
@@ -762,14 +777,6 @@ class Repository:
             formatted_free = format_file_size(free_space)
             raise self.InsufficientFreeSpaceError(formatted_required, formatted_free)
 
-    def log_storage_quota(self):
-        if self.storage_quota:
-            logger.info(
-                "Storage quota: %s out of %s used.",
-                format_file_size(self.storage_quota_use),
-                format_file_size(self.storage_quota),
-            )
-
     def compact_segments(self, threshold):
         """Compact sparse segments by copying data into new segments"""
         if not self.compact:

+ 15 - 0
src/borg/testsuite/archiver/rinfo_cmd.py

@@ -1,4 +1,5 @@
 import json
+from random import randbytes
 import unittest
 
 from ...constants import *  # NOQA
@@ -36,6 +37,20 @@ class ArchiverTestCase(ArchiverTestCaseBase):
         assert all(isinstance(o, int) for o in stats.values())
         assert all(key in stats for key in ("total_chunks", "total_size", "total_unique_chunks", "unique_size"))
 
+    def test_info_on_repository_with_storage_quota(self):
+        self.create_regular_file("file1", contents=randbytes(1000 * 1000))
+        self.cmd(f"--repo={self.repository_location}", "rcreate", RK_ENCRYPTION, "--storage-quota=1G")
+        self.cmd(f"--repo={self.repository_location}", "create", "test", "input")
+        info_repo = self.cmd(f"--repo={self.repository_location}", "rinfo")
+        assert "Storage quota: 1.00 MB used out of 1.00 GB" in info_repo
+
+    def test_info_on_repository_without_storage_quota(self):
+        self.create_regular_file("file1", contents=randbytes(1000 * 1000))
+        self.cmd(f"--repo={self.repository_location}", "rcreate", RK_ENCRYPTION)
+        self.cmd(f"--repo={self.repository_location}", "create", "test", "input")
+        info_repo = self.cmd(f"--repo={self.repository_location}", "rinfo")
+        assert "Storage quota: 1.00 MB used" in info_repo
+
 
 class RemoteArchiverTestCase(RemoteArchiverTestCaseBase, ArchiverTestCase):
     """run the same tests, but with a remote repository"""