From 67571eb0335925728fa262feceb6d75797921543 Mon Sep 17 00:00:00 2001
From: Brian Warner <warner@lothar.com>
Date: Tue, 3 Mar 2009 19:37:15 -0700
Subject: [PATCH] add more information to NotEnoughSharesError, split out new
 exceptions for no-servers and no-source-of-ueb-hash

---
 src/allmydata/immutable/download.py | 13 +++++++++----
 src/allmydata/immutable/encode.py   |  3 ++-
 src/allmydata/immutable/upload.py   |  9 +++++----
 src/allmydata/interfaces.py         | 14 +++++++++++++-
 src/allmydata/mutable/retrieve.py   |  3 ++-
 src/allmydata/test/common.py        | 10 +++++-----
 6 files changed, 36 insertions(+), 16 deletions(-)

diff --git a/src/allmydata/immutable/download.py b/src/allmydata/immutable/download.py
index 6e1593d1..70907bc5 100644
--- a/src/allmydata/immutable/download.py
+++ b/src/allmydata/immutable/download.py
@@ -11,7 +11,8 @@ from allmydata.util.assertutil import _assert, precondition
 from allmydata.util.rrefutil import ServerFailure
 from allmydata import codec, hashtree, uri
 from allmydata.interfaces import IDownloadTarget, IDownloader, IFileURI, IVerifierURI, \
-     IDownloadStatus, IDownloadResults, IValidatedThingProxy, NotEnoughSharesError
+     IDownloadStatus, IDownloadResults, IValidatedThingProxy, NotEnoughSharesError, \
+     UnableToFetchCriticalDownloadDataError
 from allmydata.immutable import layout
 from allmydata.monitor import Monitor
 from pycryptopp.cipher.aes import AES
@@ -95,7 +96,7 @@ class ValidatedThingObtainer:
                 op=self._debugname, validatedthingproxy=str(validatedthingproxy),
                 failure=f, level=level, umid="JGXxBA")
         if not self._validatedthingproxies:
-            raise NotEnoughSharesError("ran out of peers, last error was %s" % (f,))
+            raise UnableToFetchCriticalDownloadDataError("ran out of peers, last error was %s" % (f,))
         # try again with a different one
         d = self._try_the_next_one()
         return d
@@ -801,7 +802,9 @@ class CiphertextDownloader(log.PrefixingLogMixin):
             self._results.timings["peer_selection"] = now - self._started
 
         if len(self._share_buckets) < self._verifycap.needed_shares:
-            raise NotEnoughSharesError(len(self._share_buckets), self._verifycap.needed_shares)
+            raise NotEnoughSharesError("Failed to get enough shareholders",
+                                       len(self._share_buckets),
+                                       self._verifycap.needed_shares)
 
         #for s in self._share_vbuckets.values():
         #    for vb in s:
@@ -886,7 +889,9 @@ class CiphertextDownloader(log.PrefixingLogMixin):
             available_shnums = set(self._share_vbuckets.keys())
             potential_shnums = list(available_shnums - handled_shnums)
             if len(potential_shnums) < (self._verifycap.needed_shares - len(self.active_buckets)):
-                raise NotEnoughSharesError
+                have = len(potential_shnums) + len(self.active_buckets)
+                raise NotEnoughSharesError("Unable to activate enough shares",
+                                           have, self._verifycap.needed_shares)
             # For the next share, choose a primary share if available, else a randomly chosen
             # secondary share.
             potential_shnums.sort()
diff --git a/src/allmydata/immutable/encode.py b/src/allmydata/immutable/encode.py
index e68e7b2d..310e446e 100644
--- a/src/allmydata/immutable/encode.py
+++ b/src/allmydata/immutable/encode.py
@@ -488,7 +488,8 @@ class Encoder(object):
                      level=log.WEIRD, umid="TQGFRw")
         if len(self.landlords) < self.shares_of_happiness:
             msg = "lost too many shareholders during upload: %s" % why
-            raise NotEnoughSharesError(msg)
+            raise NotEnoughSharesError(msg, len(self.landlords),
+                                       self.shares_of_happiness)
         self.log("but we can still continue with %s shares, we'll be happy "
                  "with at least %s" % (len(self.landlords),
                                        self.shares_of_happiness),
diff --git a/src/allmydata/immutable/upload.py b/src/allmydata/immutable/upload.py
index 36e01e23..ec3619f6 100644
--- a/src/allmydata/immutable/upload.py
+++ b/src/allmydata/immutable/upload.py
@@ -18,7 +18,7 @@ from allmydata.util.assertutil import precondition
 from allmydata.util.rrefutil import get_versioned_remote_reference
 from allmydata.interfaces import IUploadable, IUploader, IUploadResults, \
      IEncryptedUploadable, RIEncryptedUploadable, IUploadStatus, \
-     NotEnoughSharesError, InsufficientVersionError
+     NotEnoughSharesError, InsufficientVersionError, NoServersError
 from allmydata.immutable import layout
 from pycryptopp.cipher.aes import AES
 
@@ -169,7 +169,7 @@ class Tahoe2PeerSelector:
 
         peers = client.get_permuted_peers("storage", storage_index)
         if not peers:
-            raise NotEnoughSharesError("client gave us zero peers")
+            raise NoServersError("client gave us zero peers")
 
         # this needed_hashes computation should mirror
         # Encoder.send_all_share_hash_trees. We use an IncompleteHashTree
@@ -195,7 +195,7 @@ class Tahoe2PeerSelector:
         peers = [peer for peer in peers
                  if _get_maxsize(peer) >= allocated_size]
         if not peers:
-            raise NotEnoughSharesError("no peers could accept an allocated_size of %d" % allocated_size)
+            raise NoServersError("no peers could accept an allocated_size of %d" % allocated_size)
 
         # decide upon the renewal/cancel secrets, to include them in the
         # allocat_buckets query.
@@ -298,7 +298,8 @@ class Tahoe2PeerSelector:
                 if self.last_failure_msg:
                     msg += " (%s)" % (self.last_failure_msg,)
                 log.msg(msg, level=log.UNUSUAL, parent=self._log_parent)
-                raise NotEnoughSharesError(msg)
+                raise NotEnoughSharesError(msg, placed_shares,
+                                           self.shares_of_happiness)
             else:
                 # we placed enough to be happy, so we're done
                 if self._status:
diff --git a/src/allmydata/interfaces.py b/src/allmydata/interfaces.py
index 8aa59799..abfeb1cc 100644
--- a/src/allmydata/interfaces.py
+++ b/src/allmydata/interfaces.py
@@ -715,7 +715,19 @@ class IMutableFileNode(IFileNode, IMutableFilesystemNode):
         """
 
 class NotEnoughSharesError(Exception):
-    servermap = None
+    def __init__(self, msg, got, needed):
+        Exception.__init__(self, msg)
+        self.got = got
+        self.needed = needed
+        self.servermap = None
+
+class UnableToFetchCriticalDownloadDataError(Exception):
+    """I was unable to fetch some piece of critical data which is supposed to
+    be identically present in all shares."""
+
+class NoServersError(Exception):
+    """Upload wasn't given any servers to work with, usually indicating a
+    network or Introducer problem."""
 
 class ExistingChildError(Exception):
     """A directory node was asked to add or replace a child that already
diff --git a/src/allmydata/mutable/retrieve.py b/src/allmydata/mutable/retrieve.py
index d05e5296..0a6c0ea6 100644
--- a/src/allmydata/mutable/retrieve.py
+++ b/src/allmydata/mutable/retrieve.py
@@ -466,7 +466,8 @@ class Retrieve:
             self.log(format=format,
                      level=log.WEIRD, umid="ezTfjw", **args)
             err = NotEnoughSharesError("%s, last failure: %s" %
-                                      (format % args, self._last_failure))
+                                      (format % args, self._last_failure),
+                                       len(self.shares), k)
             if self._bad_shares:
                 self.log("We found some bad shares this pass. You should "
                          "update the servermap and try again to check "
diff --git a/src/allmydata/test/common.py b/src/allmydata/test/common.py
index b4401568..d0dad7a4 100644
--- a/src/allmydata/test/common.py
+++ b/src/allmydata/test/common.py
@@ -99,7 +99,7 @@ class FakeCHKFileNode:
 
     def download(self, target):
         if self.my_uri.to_string() not in self.all_contents:
-            f = failure.Failure(NotEnoughSharesError())
+            f = failure.Failure(NotEnoughSharesError(None, 0, 3))
             target.fail(f)
             return defer.fail(f)
         data = self.all_contents[self.my_uri.to_string()]
@@ -109,14 +109,14 @@ class FakeCHKFileNode:
         return defer.maybeDeferred(target.finish)
     def download_to_data(self):
         if self.my_uri.to_string() not in self.all_contents:
-            return defer.fail(NotEnoughSharesError())
+            return defer.fail(NotEnoughSharesError(None, 0, 3))
         data = self.all_contents[self.my_uri.to_string()]
         return defer.succeed(data)
     def get_size(self):
         try:
             data = self.all_contents[self.my_uri.to_string()]
         except KeyError, le:
-            raise NotEnoughSharesError(le)
+            raise NotEnoughSharesError(le, 0, 3)
         return len(data)
     def read(self, consumer, offset=0, size=None):
         d = self.download_to_data()
@@ -269,7 +269,7 @@ class FakeMutableFileNode:
 
     def download(self, target):
         if self.storage_index not in self.all_contents:
-            f = failure.Failure(NotEnoughSharesError())
+            f = failure.Failure(NotEnoughSharesError(None, 0, 3))
             target.fail(f)
             return defer.fail(f)
         data = self.all_contents[self.storage_index]
@@ -279,7 +279,7 @@ class FakeMutableFileNode:
         return defer.maybeDeferred(target.finish)
     def download_to_data(self):
         if self.storage_index not in self.all_contents:
-            return defer.fail(NotEnoughSharesError())
+            return defer.fail(NotEnoughSharesError(None, 0, 3))
         data = self.all_contents[self.storage_index]
         return defer.succeed(data)
 
-- 
2.45.2