repairer: add basic test of repairer, move tests of immutable checker/repairer from...
authorZooko O'Whielacronx <zooko@zooko.com>
Thu, 25 Sep 2008 17:16:53 +0000 (10:16 -0700)
committerZooko O'Whielacronx <zooko@zooko.com>
Thu, 25 Sep 2008 17:16:53 +0000 (10:16 -0700)
Hm...  "Checker" ought to be renamed to "CheckerRepairer" or "Repairer" at some point...

src/allmydata/interfaces.py
src/allmydata/test/test_filenode.py
src/allmydata/test/test_immutable_checker.py [new file with mode: 0644]
src/allmydata/test/test_system.py

index c68ae11a0455856dbcd37d8e74c23695c94f945d..f2da66f87c3fa8e78f19621fe45ff1326c474f3c 100644 (file)
@@ -1477,13 +1477,8 @@ class ICheckable(Interface):
         """Like check(), but if the file/directory is not healthy, attempt to
         repair the damage.
 
-        This returns a Deferred which fires with a tuple of (pre, post), each
-        is either None or an ICheckerResults instance. For non-distributed
-        files (i.e. a LIT file) both are None. Otherwise, 'pre' is an
-        ICheckerResults representing the state of the object before any
-        repair attempt is made. If the file was unhealthy and repair was
-        attempted, 'post' will be another ICheckerResults instance with the
-        state of the object after repair."""
+        This returns a Deferred which fires with an instance of
+        ICheckAndRepairResults."""
 
 class IDeepCheckable(Interface):
     def deep_check(verify=False):
index f7dfd8bf1c7683f61c0b341941402a3e4a8db99d..16879d41fd6e2a0d35e44a91474f8d2c31a8b56b 100644 (file)
@@ -111,31 +111,7 @@ class Node(unittest.TestCase):
         v = n.get_verifier()
         self.failUnless(isinstance(v, uri.SSKVerifierURI))
 
-class Checker(unittest.TestCase):
-    def test_chk_filenode(self):
-        u = uri.CHKFileURI(key="\x00"*16,
-                           uri_extension_hash="\x00"*32,
-                           needed_shares=3,
-                           total_shares=10,
-                           size=1000)
-        c = None
-        fn1 = filenode.FileNode(u, c)
-
-        fn1.checker_class = FakeImmutableChecker
-        fn1.verifier_class = FakeImmutableVerifier
-
-        d = fn1.check()
-        def _check_checker_results(cr):
-            self.failUnless(cr.is_healthy())
-        d.addCallback(_check_checker_results)
-
-        d.addCallback(lambda res: fn1.check(verify=True))
-        d.addCallback(_check_checker_results)
-
-        # TODO: check-and-repair
-
-        return d
-
+class LiteralChecker(unittest.TestCase):
     def test_literal_filenode(self):
         DATA = "I am a short file."
         u = uri.LiteralFileURI(data=DATA)
@@ -150,35 +126,3 @@ class Checker(unittest.TestCase):
         d.addCallback(_check_checker_results)
 
         return d
-
-class FakeMutableChecker:
-    def __init__(self, node):
-        self.r = CheckerResults(node.get_storage_index())
-        self.r.set_healthy(True)
-
-    def check(self, verify):
-        return defer.succeed(self.r)
-
-class FakeMutableCheckAndRepairer:
-    def __init__(self, node):
-        cr = CheckerResults(node.get_storage_index())
-        cr.set_healthy(True)
-        self.r = CheckAndRepairResults(node.get_storage_index())
-        self.r.pre_repair_results = self.r.post_repair_results = cr
-
-    def check(self, verify):
-        return defer.succeed(self.r)
-
-class FakeImmutableChecker:
-    def __init__(self, client, storage_index, needed_shares, total_shares):
-        self.r = CheckerResults(storage_index)
-        self.r.set_healthy(True)
-
-    def start(self):
-        return defer.succeed(self.r)
-
-def FakeImmutableVerifier(client,
-                          storage_index, needed_shares, total_shares, size,
-                          ueb_hash):
-    return FakeImmutableChecker(client,
-                                storage_index, needed_shares, total_shares)
diff --git a/src/allmydata/test/test_immutable_checker.py b/src/allmydata/test/test_immutable_checker.py
new file mode 100644 (file)
index 0000000..a561cf2
--- /dev/null
@@ -0,0 +1,226 @@
+from allmydata.immutable import upload
+from allmydata.test.common import SystemTestMixin, ShareManglingMixin
+from allmydata.util import testutil
+from twisted.internet import defer
+from twisted.trial import unittest
+import random, struct
+
+class Test(ShareManglingMixin, unittest.TestCase):
+    def setUp(self):
+        # Set self.basedir to a temp dir which has the name of the current test method in its
+        # name.
+        self.basedir = self.mktemp()
+        TEST_DATA="\x02"*(upload.Uploader.URI_LIT_SIZE_THRESHOLD+1)
+
+        d = defer.maybeDeferred(SystemTestMixin.setUp, self)
+        d.addCallback(lambda x: self.set_up_nodes())
+
+        def _upload_a_file(ignored):
+            d2 = self.clients[0].upload(upload.Data(TEST_DATA, convergence=""))
+            d2.addCallback(lambda u: self.clients[0].create_node_from_uri(u.uri))
+            return d2
+        d.addCallback(_upload_a_file)
+
+        def _stash_it(filenode):
+            self.filenode = filenode
+        d.addCallback(_stash_it)
+        return d
+
+    def _delete_a_share(self, unused=None):
+        """ Exactly one bit of exactly one share on disk will be flipped (randomly selected from
+        among the bits of the 'share data' -- the verifiable bits)."""
+
+        shares = self.find_shares()
+        ks = shares.keys()
+        k = random.choice(ks)
+        del shares[k]
+        self.replace_shares(shares)
+
+        return unused
+
+    def _corrupt_a_share(self, unused=None):
+        """ Delete one share. """
+
+        shares = self.find_shares()
+        ks = shares.keys()
+        k = random.choice(ks)
+        data = shares[k]
+
+        (version, size, num_leases) = struct.unpack(">LLL", data[:0xc])
+        sharedata = data[0xc:0xc+size]
+
+        corruptedsharedata = testutil.flip_one_bit(sharedata)
+        corrupteddata = data[:0xc]+corruptedsharedata+data[0xc+size:]
+        shares[k] = corrupteddata
+
+        self.replace_shares(shares)
+
+        return unused
+
+    def test_test_code(self):
+        # The following process of stashing the shares, running
+        # replace_shares, and asserting that the new set of shares equals the
+        # old is more to test this test code than to test the Tahoe code...
+        d = defer.succeed(None)
+        d.addCallback(self.find_shares)
+        stash = [None]
+        def _stash_it(res):
+            stash[0] = res
+            return res
+
+        d.addCallback(_stash_it)
+        d.addCallback(self.replace_shares)
+
+        def _compare(res):
+            oldshares = stash[0]
+            self.failUnless(isinstance(oldshares, dict), oldshares)
+            self.failUnlessEqual(oldshares, res)
+
+        d.addCallback(self.find_shares)
+        d.addCallback(_compare)
+
+        d.addCallback(lambda ignore: self.replace_shares({}))
+        d.addCallback(self.find_shares)
+        d.addCallback(lambda x: self.failUnlessEqual(x, {}))
+
+        return d
+
+    def _count_reads(self):
+        sum_of_read_counts = 0
+        for client in self.clients:
+            counters = client.stats_provider.get_stats()['counters']
+            sum_of_read_counts += counters.get('storage_server.read', 0)
+        return sum_of_read_counts
+
+    def _count_allocates(self):
+        sum_of_allocate_counts = 0
+        for client in self.clients:
+            counters = client.stats_provider.get_stats()['counters']
+            sum_of_allocate_counts += counters.get('storage_server.allocate', 0)
+        return sum_of_allocate_counts
+
+    def test_check_without_verify(self):
+        """ Check says the file is healthy when none of the shares have been
+        touched.  It says that the file is unhealthy when all of them have
+        been removed. It says that the file is healthy if one bit of one share
+        has been flipped."""
+        d = defer.succeed(self.filenode)
+        def _check1(filenode):
+            before_check_reads = self._count_reads()
+
+            d2 = filenode.check(verify=False)
+            def _after_check(checkresults):
+                after_check_reads = self._count_reads()
+                self.failIf(after_check_reads - before_check_reads > 0, after_check_reads - before_check_reads)
+                self.failUnless(checkresults.is_healthy())
+
+            d2.addCallback(_after_check)
+            return d2
+        d.addCallback(_check1)
+
+        d.addCallback(self._corrupt_a_share)
+        def _check2(ignored):
+            before_check_reads = self._count_reads()
+            d2 = self.filenode.check(verify=False)
+
+            def _after_check(checkresults):
+                after_check_reads = self._count_reads()
+                self.failIf(after_check_reads - before_check_reads > 0, after_check_reads - before_check_reads)
+
+            d2.addCallback(_after_check)
+            return d2
+        d.addCallback(_check2)
+        return d
+
+        d.addCallback(lambda ignore: self.replace_shares({}))
+        def _check3(ignored):
+            before_check_reads = self._count_reads()
+            d2 = self.filenode.check(verify=False)
+
+            def _after_check(checkresults):
+                after_check_reads = self._count_reads()
+                self.failIf(after_check_reads - before_check_reads > 0, after_check_reads - before_check_reads)
+                self.failIf(checkresults.is_healthy())
+
+            d2.addCallback(_after_check)
+            return d2
+        d.addCallback(_check3)
+
+        return d
+
+    def test_check_with_verify(self):
+        """ Check says the file is healthy when none of the shares have been touched.  It says
+        that the file is unhealthy if one bit of one share has been flipped."""
+        # N == 10.  2 is the "efficiency leeway" -- we'll allow you to pass this test even if
+        # you trigger twice as many disk reads and blocks sends as would be optimal.
+        DELTA_READS = 10 * 2
+        d = defer.succeed(self.filenode)
+        def _check1(filenode):
+            before_check_reads = self._count_reads()
+
+            d2 = filenode.check(verify=True)
+            def _after_check(checkresults):
+                after_check_reads = self._count_reads()
+                # print "delta was ", after_check_reads - before_check_reads
+                self.failIf(after_check_reads - before_check_reads > DELTA_READS)
+                self.failUnless(checkresults.is_healthy())
+
+            d2.addCallback(_after_check)
+            return d2
+        d.addCallback(_check1)
+
+        d.addCallback(self._corrupt_a_share)
+        def _check2(ignored):
+            before_check_reads = self._count_reads()
+            d2 = self.filenode.check(verify=True)
+
+            def _after_check(checkresults):
+                after_check_reads = self._count_reads()
+                # print "delta was ", after_check_reads - before_check_reads
+                self.failIf(after_check_reads - before_check_reads > DELTA_READS)
+                self.failIf(checkresults.is_healthy())
+
+            d2.addCallback(_after_check)
+            return d2
+        d.addCallback(_check2)
+        return d
+    test_check_with_verify.todo = "We haven't implemented a verifier this thorough yet."
+
+    def test_repair(self):
+        """ Repair replaces a share that got deleted. """
+        # N == 10.  2 is the "efficiency leeway" -- we'll allow you to pass this test even if
+        # you trigger twice as many disk reads and blocks sends as would be optimal.
+        DELTA_READS = 10 * 2
+        # We'll allow you to pass this test only if you repair the missing share using only a
+        # single allocate.
+        DELTA_ALLOCATES = 1
+
+        d = defer.succeed(self.filenode)
+        d.addCallback(self._delete_a_share)
+
+        def _repair(filenode):
+            before_repair_reads = self._count_reads()
+            before_repair_allocates = self._count_allocates()
+
+            d2 = filenode.check_and_repair(verify=False)
+            def _after_repair(checkandrepairresults):
+                prerepairres = checkandrepairresults.get_pre_repair_results()
+                postrepairres = checkandrepairresults.get_post_repair_results()
+                after_repair_reads = self._count_reads()
+                after_repair_allocates = self._count_allocates()
+
+                # print "delta was ", after_repair_reads - before_repair_reads, after_repair_allocates - before_repair_allocates
+                self.failIf(after_repair_reads - before_repair_reads > DELTA_READS)
+                self.failIf(after_repair_allocates - before_repair_allocates > DELTA_ALLOCATES)
+                self.failIf(prerepairres.is_healthy())
+                self.failUnless(postrepairres.is_healthy())
+
+                # Now we inspect the filesystem to make sure that it is really there.
+                shares = self.find_shares()
+                self.failIf(len(shares) < 10)
+
+            d2.addCallback(_after_repair)
+            return d2
+        d.addCallback(_repair)
+        return d
+    test_repair.todo = "We haven't implemented a checker yet."
index bb29acb5b60df747d6dfd448b29fb73be5425955..530b2052e08f7ff95de82ce72a013e9f30e8c6ee 100644 (file)
@@ -1695,165 +1695,6 @@ class SystemTest(SystemTestMixin, unittest.TestCase):
         return d
 
 
-class ImmutableChecker(ShareManglingMixin, unittest.TestCase):
-    def setUp(self):
-        # Set self.basedir to a temp dir which has the name of the current test method in its
-        # name.
-        self.basedir = self.mktemp()
-        TEST_DATA="\x02"*(upload.Uploader.URI_LIT_SIZE_THRESHOLD+1)
-
-        d = defer.maybeDeferred(SystemTestMixin.setUp, self)
-        d.addCallback(lambda x: self.set_up_nodes())
-
-        def _upload_a_file(ignored):
-            d2 = self.clients[0].upload(upload.Data(TEST_DATA, convergence=""))
-            d2.addCallback(lambda u: self.clients[0].create_node_from_uri(u.uri))
-            return d2
-        d.addCallback(_upload_a_file)
-
-        def _stash_it(filenode):
-            self.filenode = filenode
-        d.addCallback(_stash_it)
-        return d
-
-    def _corrupt_a_share(self, unused=None):
-        """ Exactly one bit of exactly one share on disk will be flipped (randomly selected from
-        among the bits of the 'share data' -- the verifiable bits)."""
-
-        shares = self.find_shares()
-        ks = shares.keys()
-        k = random.choice(ks)
-        data = shares[k]
-
-        (version, size, num_leases) = struct.unpack(">LLL", data[:0xc])
-        sharedata = data[0xc:0xc+size]
-
-        corruptedsharedata = testutil.flip_one_bit(sharedata)
-        corrupteddata = data[:0xc]+corruptedsharedata+data[0xc+size:]
-        shares[k] = corrupteddata
-
-        self.replace_shares(shares)
-
-    def test_test_code(self):
-        # The following process of stashing the shares, running
-        # replace_shares, and asserting that the new set of shares equals the
-        # old is more to test this test code than to test the Tahoe code...
-        d = defer.succeed(None)
-        d.addCallback(self.find_shares)
-        stash = [None]
-        def _stash_it(res):
-            stash[0] = res
-            return res
-
-        d.addCallback(_stash_it)
-        d.addCallback(self.replace_shares)
-
-        def _compare(res):
-            oldshares = stash[0]
-            self.failUnless(isinstance(oldshares, dict), oldshares)
-            self.failUnlessEqual(oldshares, res)
-
-        d.addCallback(self.find_shares)
-        d.addCallback(_compare)
-
-        d.addCallback(lambda ignore: self.replace_shares({}))
-        d.addCallback(self.find_shares)
-        d.addCallback(lambda x: self.failUnlessEqual(x, {}))
-
-        return d
-
-    def _count_reads(self):
-        sum_of_read_counts = 0
-        for client in self.clients:
-            counters = client.stats_provider.get_stats()['counters']
-            sum_of_read_counts += counters.get('storage_server.read', 0)
-        return sum_of_read_counts
-
-    def test_check_without_verify(self):
-        """ Check says the file is healthy when none of the shares have been
-        touched.  It says that the file is unhealthy when all of them have
-        been removed. It says that the file is healthy if one bit of one share
-        has been flipped."""
-        d = defer.succeed(self.filenode)
-        def _check1(filenode):
-            before_check_reads = self._count_reads()
-
-            d2 = filenode.check(verify=False)
-            def _after_check(checkresults):
-                after_check_reads = self._count_reads()
-                self.failIf(after_check_reads - before_check_reads > 0, after_check_reads - before_check_reads)
-                self.failUnless(checkresults.is_healthy())
-
-            d2.addCallback(_after_check)
-            return d2
-        d.addCallback(_check1)
-
-        d.addCallback(self._corrupt_a_share)
-        def _check2(ignored):
-            before_check_reads = self._count_reads()
-            d2 = self.filenode.check(verify=False)
-
-            def _after_check(checkresults):
-                after_check_reads = self._count_reads()
-                self.failIf(after_check_reads - before_check_reads > 0, after_check_reads - before_check_reads)
-
-            d2.addCallback(_after_check)
-            return d2
-        d.addCallback(_check2)
-        return d
-
-        d.addCallback(lambda ignore: self.replace_shares({}))
-        def _check3(ignored):
-            before_check_reads = self._count_reads()
-            d2 = self.filenode.check(verify=False)
-
-            def _after_check(checkresults):
-                after_check_reads = self._count_reads()
-                self.failIf(after_check_reads - before_check_reads > 0, after_check_reads - before_check_reads)
-                self.failIf(checkresults.is_healthy())
-
-            d2.addCallback(_after_check)
-            return d2
-        d.addCallback(_check3)
-
-        return d
-
-    def test_check_with_verify(self):
-        """ Check says the file is healthy when none of the shares have been touched.  It says
-        that the file is unhealthy if one bit of one share has been flipped."""
-        DELTA_READS = 10 * 2 # N == 10
-        d = defer.succeed(self.filenode)
-        def _check1(filenode):
-            before_check_reads = self._count_reads()
-
-            d2 = filenode.check(verify=True)
-            def _after_check(checkresults):
-                after_check_reads = self._count_reads()
-                # print "delta was ", after_check_reads - before_check_reads
-                self.failIf(after_check_reads - before_check_reads > DELTA_READS)
-                self.failUnless(checkresults.is_healthy())
-
-            d2.addCallback(_after_check)
-            return d2
-        d.addCallback(_check1)
-
-        d.addCallback(self._corrupt_a_share)
-        def _check2(ignored):
-            before_check_reads = self._count_reads()
-            d2 = self.filenode.check(verify=True)
-
-            def _after_check(checkresults):
-                after_check_reads = self._count_reads()
-                # print "delta was ", after_check_reads - before_check_reads
-                self.failIf(after_check_reads - before_check_reads > DELTA_READS)
-                self.failIf(checkresults.is_healthy())
-
-            d2.addCallback(_after_check)
-            return d2
-        d.addCallback(_check2)
-        return d
-    test_check_with_verify.todo = "We haven't implemented a verifier this thorough yet."
-
 class MutableChecker(SystemTestMixin, unittest.TestCase):
 
     def _run_cli(self, argv):