Repository: pulp/pulpcore

Issue:
When an error occurs during content sync, due to hash conflicts, report conflicting hashes
**Is your feature request related to a problem? Please describe.**
Recently had an issue relating to synchronising content, due to a conflict between an expected hash, and an actual hash of the data recieved.
Currently, we print out the URL of the content being grabbed, but no information relating to the conflicting hashses.

**Describe the solution you'd like**
I would like the error message to be extended to include both the expected hash as well as the hash of the retrieved data.

**Describe alternatives you've considered**
Spending a lot of time running around in a circle

**Additional context**
After making some code changes to achieve the above, it made tracking down https://github.com/pulp/pulp_rpm/issues/2407 much much much easier.


Discussion:
Moving to pulpcore to be implemented in the core downloader error-reporting

Resolving patch:
diff --git a/pulpcore/app/models/content.py b/pulpcore/app/models/content.py
--- a/pulpcore/app/models/content.py
+++ b/pulpcore/app/models/content.py
@@ -330,7 +330,7 @@ def init_and_validate(file, expected_digests=None, expected_size=None):
 
         if expected_size:
             if size != expected_size:
-                raise SizeValidationError()
+                raise SizeValidationError(size, expected_size)
 
         if expected_digests:
             for algorithm, expected_digest in expected_digests.items():
@@ -340,8 +340,9 @@ def init_and_validate(file, expected_digests=None, expected_size=None):
                             algorithm
                         )
                     )
-                if expected_digest != hashers[algorithm].hexdigest():
-                    raise DigestValidationError()
+                actual_digest = hashers[algorithm].hexdigest()
+                if expected_digest != actual_digest:
+                    raise DigestValidationError(actual_digest, expected_digest)
 
         attributes = {"size": size, "file": file}
         for algorithm in Artifact.DIGEST_FIELDS:
@@ -443,7 +444,7 @@ def init_and_validate(file, expected_digests=None, expected_size=None):
 
         if expected_size:
             if size != expected_size:
-                raise SizeValidationError()
+                raise SizeValidationError(size, expected_size)
 
         if expected_digests:
             for algorithm, expected_digest in expected_digests.items():
@@ -453,8 +454,9 @@ def init_and_validate(file, expected_digests=None, expected_size=None):
                             algorithm
                         )
                     )
-                if expected_digest != hashers[algorithm].hexdigest():
-                    raise DigestValidationError()
+                actual_digest = hashers[algorithm].hexdigest()
+                if expected_digest != actual_digest:
+                    raise DigestValidationError(actual_digest, expected_digest)
 
         return PulpTemporaryFile(file=file)
 
diff --git a/pulpcore/download/base.py b/pulpcore/download/base.py
--- a/pulpcore/download/base.py
+++ b/pulpcore/download/base.py
@@ -219,8 +219,9 @@ def validate_digests(self):
         """
         if self.expected_digests:
             for algorithm, expected_digest in self.expected_digests.items():
-                if expected_digest != self._digests[algorithm].hexdigest():
-                    raise DigestValidationError(self.url)
+                actual_digest = self._digests[algorithm].hexdigest()
+                if actual_digest != expected_digest:
+                    raise DigestValidationError(actual_digest, expected_digest, url=self.url)
 
     def validate_size(self):
         """
@@ -232,8 +233,10 @@ def validate_size(self):
                 :meth:`~pulpcore.plugin.download.BaseDownloader.handle_data`.
         """
         if self.expected_size:
-            if self._size != self.expected_size:
-                raise SizeValidationError(self.url)
+            actual_size = self._size
+            expected_size = self.expected_size
+            if actual_size != expected_size:
+                raise SizeValidationError(actual_size, expected_size, url=self.url)
 
     async def run(self, extra_data=None):
         """
diff --git a/pulpcore/exceptions/validation.py b/pulpcore/exceptions/validation.py
--- a/pulpcore/exceptions/validation.py
+++ b/pulpcore/exceptions/validation.py
@@ -16,17 +16,24 @@ class DigestValidationError(ValidationError):
     Raised when a file fails to validate a digest checksum.
     """
 
-    def __init__(self, *args):
+    def __init__(self, actual, expected, *args, url=None, **kwargs):
         super().__init__("PLP0003")
-        if args:
-            self.url = args[0]
+        self.url = url
+        self.actual = actual
+        self.expected = expected
 
     def __str__(self):
-        if hasattr(self, "url"):
-            return _("A file located at the url {} failed validation due to checksum.").format(
-                self.url
+        if self.url:
+            msg = _(
+                "A file located at the url {url} failed validation due to checksum. "
+                "Expected '{expected}', Actual '{actual}'"
             )
-        return _("A file failed validation due to checksum.")
+            return msg.format(url=self.url, expected=self.expected, actual=self.actual)
+        else:
+            msg = _(
+                "A file failed validation due to checksum. Expected '{expected}', Actual '{actual}'"
+            )
+            return msg.format(expected=self.expected, actual=self.actual)
 
 
 class SizeValidationError(ValidationError):
@@ -34,15 +41,24 @@ class SizeValidationError(ValidationError):
     Raised when a file fails to validate a size checksum.
     """
 
-    def __init__(self, *args):
+    def __init__(self, actual, expected, *args, url=None, **kwargs):
         super().__init__("PLP0004")
-        if args:
-            self.url = args[0]
+        self.url = url
+        self.actual = actual
+        self.expected = expected
 
     def __str__(self):
-        if hasattr(self, "url"):
-            return _("A file located at the url {} failed validation due to size.").format(self.url)
-        return _("A file failed validation due to size.")
+        if self.url:
+            msg = _(
+                "A file located at the url {url} failed validation due to size. "
+                "Expected '{expected}', Actual '{actual}'"
+            )
+            return msg.format(url=self.url, expected=self.expected, actual=self.actual)
+        else:
+            msg = _(
+                "A file failed validation due to size. Expected '{expected}', Actual '{actual}'"
+            )
+            return msg.format(expected=self.expected, actual=self.actual)
 
 
 class MissingDigestValidationError(Exception):


Test patch: