From 9289433ba38dd724834cb6e97bc5907c3e42cd10 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Wed, 16 Jul 2008 18:20:57 -0700 Subject: [PATCH] first pass at deep-checker, no webapi yet, probably big problems with it, only minimal tests --- src/allmydata/dirnode.py | 43 +++++++++++++++++++++++++++++ src/allmydata/immutable/checker.py | 39 +++++++++++++++++++++++++- src/allmydata/immutable/filenode.py | 11 +++++++- src/allmydata/interfaces.py | 30 ++++++++++++++++++++ src/allmydata/mutable/node.py | 10 +++++++ src/allmydata/test/test_dirnode.py | 23 +++++++++++++++ 6 files changed, 154 insertions(+), 2 deletions(-) diff --git a/src/allmydata/dirnode.py b/src/allmydata/dirnode.py index 2e73cafe..d8d3e93b 100644 --- a/src/allmydata/dirnode.py +++ b/src/allmydata/dirnode.py @@ -9,6 +9,7 @@ from allmydata.mutable.node import MutableFileNode from allmydata.interfaces import IMutableFileNode, IDirectoryNode,\ IURI, IFileNode, IMutableFileURI, IVerifierURI, IFilesystemNode, \ ExistingChildError, ICheckable +from allmydata.immutable.checker import DeepCheckResults from allmydata.util import hashutil, mathutil from allmydata.util.hashutil import netstring from allmydata.util.limiter import ConcurrencyLimiter @@ -533,6 +534,48 @@ class NewDirectoryNode: d.addCallback(_got_list) return d + def deep_check(self, verify=False, repair=False): + results = DeepCheckResults() + found = set() + found.add(self.get_verifier()) + + limiter = ConcurrencyLimiter(10) + + d = self._add_check_from_node(self, results, limiter, verify, repair) + d.addCallback(lambda res: + self._add_deepcheck_from_dirnode(self, + found, results, limiter, + verify, repair)) + d.addCallback(lambda res: results) + return d + + def _add_check_from_node(self, node, results, limiter, verify, repair): + d = limiter.add(node.check, verify, repair) + d.addCallback(results.add_check) + return d + + def _add_deepcheck_from_dirnode(self, node, found, results, limiter, + verify, repair): + d = limiter.add(node.list) + def _got_list(children): + dl = [] + for name, (child, metadata) in children.iteritems(): + verifier = child.get_verifier() + if verifier in found: + # avoid loops + continue + dl.append(self._add_check_from_node(child, + results, limiter, + verify, repair)) + if IDirectoryNode.providedBy(child): + dl.append(self._add_deepcheck_from_node(child, found, + results, limiter, + verify, repair)) + if dl: + return defer.DeferredList(dl) + d.addCallback(_got_list) + return d + class DeepStats: def __init__(self): self.stats = {} diff --git a/src/allmydata/immutable/checker.py b/src/allmydata/immutable/checker.py index c3269217..33a12931 100644 --- a/src/allmydata/immutable/checker.py +++ b/src/allmydata/immutable/checker.py @@ -10,7 +10,8 @@ from zope.interface import implements from twisted.internet import defer from twisted.python import log from allmydata import storage -from allmydata.interfaces import IVerifierURI, ICheckerResults +from allmydata.interfaces import IVerifierURI, \ + ICheckerResults, IDeepCheckResults from allmydata.immutable import download from allmydata.util import hashutil, base32 @@ -44,6 +45,42 @@ class Results: s += "Not Healthy!\n" return s +class DeepCheckResults: + implements(IDeepCheckResults) + + def __init__(self): + self.objects_checked = 0 + self.objects_healthy = 0 + self.repairs_attempted = 0 + self.repairs_successful = 0 + self.problems = [] + self.server_problems = {} + + def add_check(self, r): + self.objects_checked += 1 + if r.is_healthy: + self.objects_healthy += 1 + else: + self.problems.append(r) + + def add_repair(self, is_successful): + self.repairs_attempted += 1 + if is_successful: + self.repairs_successful += 1 + + def count_objects_checked(self): + return self.objects_checked + def count_objects_healthy(self): + return self.objects_healthy + def count_repairs_attempted(self): + return self.repairs_attempted + def count_repairs_successful(self): + return self.repairs_successful + def get_server_problems(self): + return self.server_problems + def get_problems(self): + return self.problems + class SimpleCHKFileChecker: """Return a list of (needed, total, found, sharemap), where sharemap maps diff --git a/src/allmydata/immutable/filenode.py b/src/allmydata/immutable/filenode.py index c3caae73..1ed88982 100644 --- a/src/allmydata/immutable/filenode.py +++ b/src/allmydata/immutable/filenode.py @@ -3,7 +3,7 @@ from zope.interface import implements from twisted.internet import defer from allmydata.interfaces import IFileNode, IFileURI, IURI, ICheckable from allmydata import uri -from allmydata.immutable.checker import Results, \ +from allmydata.immutable.checker import Results, DeepCheckResults, \ SimpleCHKFileChecker, SimpleCHKFileVerifier class FileNode: @@ -52,6 +52,15 @@ class FileNode: v = SimpleCHKFileChecker(peer_getter, vcap) return v.check() + def deep_check(self, verify=False, repair=False): + d = self.check(verify, repair) + def _done(r): + dr = DeepCheckResults() + dr.add_check(r) + return dr + d.addCallback(_done) + return d + def download(self, target): downloader = self._client.getServiceNamed("downloader") return downloader.download(self.uri, target) diff --git a/src/allmydata/interfaces.py b/src/allmydata/interfaces.py index c03d4d18..d63dd9b2 100644 --- a/src/allmydata/interfaces.py +++ b/src/allmydata/interfaces.py @@ -1473,6 +1473,15 @@ class ICheckable(Interface): taken. """ + def deep_check(verify=False, repair=False): + """Check upon the health of me and everything I can reach. + + This is a recursive form of check(), useable on dirnodes. (it can be + called safely on filenodes too, but only checks the one object). + + I return a Deferred that fires with an IDeepCheckResults object. + """ + class ICheckerResults(Interface): """I contain the detailed results of a check/verify/repair operation. @@ -1506,6 +1515,27 @@ class ICheckerResults(Interface): # same nodeid, they will fail as a pair, and overall reliability is # decreased. +class IDeepCheckResults(Interface): + """I contain the results of a deep-check operation. + + This is returned by a call to ICheckable.deep_check(). + """ + + def count_objects_checked(): + """Return the number of objects that were checked.""" + def count_objects_healthy(): + """Return the number of objects that were fully healthy.""" + def count_repairs_attempted(): + """Return the number of repair operations that were attempted.""" + def count_repairs_successful(): + """Return the number of repair operations that succeeded in bringing + the object back up to full health.""" + def get_server_problems(): + """Return a dict, mapping server nodeid to a count of how many + problems involved that server.""" + def get_problems(): + """Return a list of ICheckerResults, one for each object that + was not fully healthy.""" class IClient(Interface): diff --git a/src/allmydata/mutable/node.py b/src/allmydata/mutable/node.py index cd9f9763..38c132a1 100644 --- a/src/allmydata/mutable/node.py +++ b/src/allmydata/mutable/node.py @@ -11,6 +11,7 @@ from allmydata.util import hashutil from allmydata.util.assertutil import precondition from allmydata.uri import WriteableSSKFileURI from allmydata.immutable.encode import NotEnoughSharesError +from allmydata.immutable.checker import DeepCheckResults from pycryptopp.publickey import rsa from pycryptopp.cipher.aes import AES @@ -240,6 +241,15 @@ class MutableFileNode: checker = MutableChecker(self) return checker.check(verify, repair) + def deep_check(self, verify=False, repair=False): + d = self.check(verify, repair) + def _done(r): + dr = DeepCheckResults() + dr.add_check(r) + return dr + d.addCallback(_done) + return d + # allow the use of IDownloadTarget def download(self, target): # fake it. TODO: make this cleaner. diff --git a/src/allmydata/test/test_dirnode.py b/src/allmydata/test/test_dirnode.py index 4aba00c4..044a8485 100644 --- a/src/allmydata/test/test_dirnode.py +++ b/src/allmydata/test/test_dirnode.py @@ -117,6 +117,29 @@ class Dirnode(unittest.TestCase, testutil.ShouldFailMixin, testutil.StallMixin): d.addCallback(_done) return d + def _test_deepcheck_create(self): + d = self.client.create_empty_dirnode() + def _created_root(rootnode): + self._rootnode = rootnode + d.addCallback(_created_root) + def _done(res): + return self._rootnode + d.addCallback(_done) + return d + + def test_deepcheck(self): + d = self._test_deepcheck_create() + d.addCallback(lambda rootnode: rootnode.deep_check()) + def _check_results(r): + self.failUnlessEqual(r.count_objects_checked(), 1) + self.failUnlessEqual(r.count_objects_healthy(), 1) + self.failUnlessEqual(r.count_repairs_attempted(), 0) + self.failUnlessEqual(r.count_repairs_successful(), 0) + self.failUnlessEqual(len(r.get_server_problems()), 0) + self.failUnlessEqual(len(r.get_problems()), 0) + d.addCallback(_check_results) + return d + def test_readonly(self): fileuri = make_chk_file_uri(1234) filenode = self.client.create_node_from_uri(fileuri) -- 2.45.2