]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blobdiff - src/allmydata/test/test_system.py
After a server disconnects, make the IServer retain the dead RemoteReference, and...
[tahoe-lafs/tahoe-lafs.git] / src / allmydata / test / test_system.py
index 93a71a0a28905b5dc4067f717993a564a5e95174..a9d20eafd17e3b66ff34800f029b5d60317e66b9 100644 (file)
@@ -4,12 +4,14 @@ from cStringIO import StringIO
 from twisted.trial import unittest
 from twisted.internet import defer
 from twisted.internet import threads # CLI tests use deferToThread
+
 import allmydata
 from allmydata import uri
 from allmydata.storage.mutable import MutableShareFile
 from allmydata.storage.server import si_a2b
 from allmydata.immutable import offloaded, upload
-from allmydata.immutable.filenode import ImmutableFileNode, LiteralFileNode
+from allmydata.immutable.literal import LiteralFileNode
+from allmydata.immutable.filenode import ImmutableFileNode
 from allmydata.util import idlib, mathutil
 from allmydata.util import log, base32
 from allmydata.util.encodingutil import quote_output, unicode_to_argv, get_filesystem_encoding
@@ -21,13 +23,17 @@ from allmydata.interfaces import IDirectoryNode, IFileNode, \
 from allmydata.monitor import Monitor
 from allmydata.mutable.common import NotWriteableError
 from allmydata.mutable import layout as mutable_layout
-from foolscap.api import DeadReferenceError
+from allmydata.mutable.publish import MutableData
+from foolscap.api import DeadReferenceError, fireEventually
 from twisted.python.failure import Failure
 from twisted.web.client import getPage
 from twisted.web.error import Error
 
 from allmydata.test.common import SystemTestMixin
 
+# TODO: move this to common or common_util
+from allmydata.test.test_runner import RunBinTahoeMixin
+
 LARGE_DATA = """
 This is some data to publish to the remote grid.., which needs to be large
 enough to not fit inside a LIT uri.
@@ -46,7 +52,7 @@ class CountingDataUploadable(upload.Data):
                 self.interrupt_after_d.callback(self)
         return upload.Data.read(self, length)
 
-class SystemTest(SystemTestMixin, unittest.TestCase):
+class SystemTest(SystemTestMixin, RunBinTahoeMixin, unittest.TestCase):
     timeout = 3600 # It takes longer than 960 seconds on Zandr's ARM box.
 
     def test_connections(self):
@@ -60,7 +66,7 @@ class SystemTest(SystemTestMixin, unittest.TestCase):
                 all_peerids = c.get_storage_broker().get_all_serverids()
                 self.failUnlessEqual(len(all_peerids), self.numclients+1)
                 sb = c.storage_broker
-                permuted_peers = sb.get_servers_for_index("a")
+                permuted_peers = sb.get_servers_for_psi("a")
                 self.failUnlessEqual(len(permuted_peers), self.numclients+1)
 
         d.addCallback(_check)
@@ -96,7 +102,7 @@ class SystemTest(SystemTestMixin, unittest.TestCase):
                 all_peerids = c.get_storage_broker().get_all_serverids()
                 self.failUnlessEqual(len(all_peerids), self.numclients)
                 sb = c.storage_broker
-                permuted_peers = sb.get_servers_for_index("a")
+                permuted_peers = sb.get_servers_for_psi("a")
                 self.failUnlessEqual(len(permuted_peers), self.numclients)
         d.addCallback(_check_connections)
 
@@ -116,7 +122,7 @@ class SystemTest(SystemTestMixin, unittest.TestCase):
             return d1
         d.addCallback(_do_upload)
         def _upload_done(results):
-            theuri = results.uri
+            theuri = results.get_uri()
             log.msg("upload finished: uri is %s" % (theuri,))
             self.uri = theuri
             assert isinstance(self.uri, str), self.uri
@@ -188,7 +194,7 @@ class SystemTest(SystemTestMixin, unittest.TestCase):
                     facility="tahoe.tests")
             d1 = download_to_data(badnode)
             def _baduri_should_fail(res):
-                log.msg("finished downloading non-existend URI",
+                log.msg("finished downloading non-existent URI",
                         level=log.UNUSUAL, facility="tahoe.tests")
                 self.failUnless(isinstance(res, Failure))
                 self.failUnless(res.check(NoSharesError),
@@ -207,12 +213,18 @@ class SystemTest(SystemTestMixin, unittest.TestCase):
             self.extra_node.DEFAULT_ENCODING_PARAMETERS['happy'] = 5
         d.addCallback(_added)
 
+        def _has_helper():
+            uploader = self.extra_node.getServiceNamed("uploader")
+            furl, connected = uploader.get_helper_info()
+            return connected
+        d.addCallback(lambda ign: self.poll(_has_helper))
+
         HELPER_DATA = "Data that needs help to upload" * 1000
         def _upload_with_helper(res):
             u = upload.Data(HELPER_DATA, convergence=convergence)
             d = self.extra_node.upload(u)
             def _uploaded(results):
-                n = self.clients[1].create_node_from_uri(results.uri)
+                n = self.clients[1].create_node_from_uri(results.get_uri())
                 return download_to_data(n)
             d.addCallback(_uploaded)
             def _check(newdata):
@@ -226,7 +238,7 @@ class SystemTest(SystemTestMixin, unittest.TestCase):
             u.debug_stash_RemoteEncryptedUploadable = True
             d = self.extra_node.upload(u)
             def _uploaded(results):
-                n = self.clients[1].create_node_from_uri(results.uri)
+                n = self.clients[1].create_node_from_uri(results.get_uri())
                 return download_to_data(n)
             d.addCallback(_uploaded)
             def _check(newdata):
@@ -238,17 +250,22 @@ class SystemTest(SystemTestMixin, unittest.TestCase):
         if convergence is not None:
             d.addCallback(_upload_duplicate_with_helper)
 
+        d.addCallback(fireEventually)
+
         def _upload_resumable(res):
             DATA = "Data that needs help to upload and gets interrupted" * 1000
             u1 = CountingDataUploadable(DATA, convergence=convergence)
             u2 = CountingDataUploadable(DATA, convergence=convergence)
 
             # we interrupt the connection after about 5kB by shutting down
-            # the helper, then restartingit.
+            # the helper, then restarting it.
             u1.interrupt_after = 5000
             u1.interrupt_after_d = defer.Deferred()
-            u1.interrupt_after_d.addCallback(lambda res:
-                                             self.bounce_client(0))
+            bounced_d = defer.Deferred()
+            def _do_bounce(res):
+                d = self.bounce_client(0)
+                d.addBoth(bounced_d.callback)
+            u1.interrupt_after_d.addCallback(_do_bounce)
 
             # sneak into the helper and reduce its chunk size, so that our
             # debug_interrupt will sever the connection on about the fifth
@@ -259,34 +276,27 @@ class SystemTest(SystemTestMixin, unittest.TestCase):
             # this same test run, but I'm not currently worried about it.
             offloaded.CHKCiphertextFetcher.CHUNK_SIZE = 1000
 
-            d = self.extra_node.upload(u1)
-
-            def _should_not_finish(res):
-                self.fail("interrupted upload should have failed, not finished"
-                          " with result %s" % (res,))
-            def _interrupted(f):
-                f.trap(DeadReferenceError)
-
-                # make sure we actually interrupted it before finishing the
-                # file
-                self.failUnless(u1.bytes_read < len(DATA),
-                                "read %d out of %d total" % (u1.bytes_read,
-                                                             len(DATA)))
-
-                log.msg("waiting for reconnect", level=log.NOISY,
-                        facility="tahoe.test.test_system")
-                # now, we need to give the nodes a chance to notice that this
-                # connection has gone away. When this happens, the storage
-                # servers will be told to abort their uploads, removing the
-                # partial shares. Unfortunately this involves TCP messages
-                # going through the loopback interface, and we can't easily
-                # predict how long that will take. If it were all local, we
-                # could use fireEventually() to stall. Since we don't have
-                # the right introduction hooks, the best we can do is use a
-                # fixed delay. TODO: this is fragile.
-                u1.interrupt_after_d.addCallback(self.stall, 2.0)
-                return u1.interrupt_after_d
-            d.addCallbacks(_should_not_finish, _interrupted)
+            upload_d = self.extra_node.upload(u1)
+            # The upload will start, and bounce_client() will be called after
+            # about 5kB. bounced_d will fire after bounce_client() finishes
+            # shutting down and restarting the node.
+            d = bounced_d
+            def _bounced(ign):
+                # By this point, the upload should have failed because of the
+                # interruption. upload_d will fire in a moment
+                def _should_not_finish(res):
+                    self.fail("interrupted upload should have failed, not"
+                              " finished with result %s" % (res,))
+                def _interrupted(f):
+                    f.trap(DeadReferenceError)
+                    # make sure we actually interrupted it before finishing
+                    # the file
+                    self.failUnless(u1.bytes_read < len(DATA),
+                                    "read %d out of %d total" %
+                                    (u1.bytes_read, len(DATA)))
+                upload_d.addCallbacks(_should_not_finish, _interrupted)
+                return upload_d
+            d.addCallback(_bounced)
 
             def _disconnected(res):
                 # check to make sure the storage servers aren't still hanging
@@ -300,13 +310,12 @@ class SystemTest(SystemTestMixin, unittest.TestCase):
                     self.failIf(os.path.exists(incdir) and os.listdir(incdir))
             d.addCallback(_disconnected)
 
-            # then we need to give the reconnector a chance to
-            # reestablish the connection to the helper.
             d.addCallback(lambda res:
-                          log.msg("wait_for_connections", level=log.NOISY,
+                          log.msg("wait_for_helper", level=log.NOISY,
                                   facility="tahoe.test.test_system"))
-            d.addCallback(lambda res: self.wait_for_connections())
-
+            # then we need to wait for the extra node to reestablish its
+            # connection to the helper.
+            d.addCallback(lambda ign: self.poll(_has_helper))
 
             d.addCallback(lambda res:
                           log.msg("uploading again", level=log.NOISY,
@@ -314,13 +323,13 @@ class SystemTest(SystemTestMixin, unittest.TestCase):
             d.addCallback(lambda res: self.extra_node.upload(u2))
 
             def _uploaded(results):
-                cap = results.uri
+                cap = results.get_uri()
                 log.msg("Second upload complete", level=log.NOISY,
                         facility="tahoe.test.test_system")
 
                 # this is really bytes received rather than sent, but it's
                 # convenient and basically measures the same thing
-                bytes_sent = results.ciphertext_fetched
+                bytes_sent = results.get_ciphertext_fetched()
                 self.failUnless(isinstance(bytes_sent, (int, long)), bytes_sent)
 
                 # We currently don't support resumption of upload if the data is
@@ -458,15 +467,18 @@ class SystemTest(SystemTestMixin, unittest.TestCase):
     def test_mutable(self):
         self.basedir = "system/SystemTest/test_mutable"
         DATA = "initial contents go here."  # 25 bytes % 3 != 0
+        DATA_uploadable = MutableData(DATA)
         NEWDATA = "new contents yay"
+        NEWDATA_uploadable = MutableData(NEWDATA)
         NEWERDATA = "this is getting old"
+        NEWERDATA_uploadable = MutableData(NEWERDATA)
 
         d = self.set_up_nodes(use_key_generator=True)
 
         def _create_mutable(res):
             c = self.clients[0]
             log.msg("starting create_mutable_file")
-            d1 = c.create_mutable_file(DATA)
+            d1 = c.create_mutable_file(DATA_uploadable)
             def _done(res):
                 log.msg("DONE: %s" % (res,))
                 self._mutable_node_1 = res
@@ -553,7 +565,7 @@ class SystemTest(SystemTestMixin, unittest.TestCase):
             self.failUnlessEqual(res, DATA)
             # replace the data
             log.msg("starting replace1")
-            d1 = newnode.overwrite(NEWDATA)
+            d1 = newnode.overwrite(NEWDATA_uploadable)
             d1.addCallback(lambda res: newnode.download_best_version())
             return d1
         d.addCallback(_check_download_3)
@@ -567,7 +579,7 @@ class SystemTest(SystemTestMixin, unittest.TestCase):
             newnode2 = self.clients[3].create_node_from_uri(uri)
             self._newnode3 = self.clients[3].create_node_from_uri(uri)
             log.msg("starting replace2")
-            d1 = newnode1.overwrite(NEWERDATA)
+            d1 = newnode1.overwrite(NEWERDATA_uploadable)
             d1.addCallback(lambda res: newnode2.download_best_version())
             return d1
         d.addCallback(_check_download_4)
@@ -637,7 +649,7 @@ class SystemTest(SystemTestMixin, unittest.TestCase):
         def _check_empty_file(res):
             # make sure we can create empty files, this usually screws up the
             # segsize math
-            d1 = self.clients[2].create_mutable_file("")
+            d1 = self.clients[2].create_mutable_file(MutableData(""))
             d1.addCallback(lambda newnode: newnode.download_best_version())
             d1.addCallback(lambda res: self.failUnlessEqual("", res))
             return d1
@@ -668,7 +680,8 @@ class SystemTest(SystemTestMixin, unittest.TestCase):
                                  self.key_generator_svc.key_generator.pool_size + size_delta)
 
         d.addCallback(check_kg_poolsize, 0)
-        d.addCallback(lambda junk: self.clients[3].create_mutable_file('hello, world'))
+        d.addCallback(lambda junk:
+            self.clients[3].create_mutable_file(MutableData('hello, world')))
         d.addCallback(check_kg_poolsize, -1)
         d.addCallback(lambda junk: self.clients[3].create_dirnode())
         d.addCallback(check_kg_poolsize, -2)
@@ -752,16 +765,39 @@ class SystemTest(SystemTestMixin, unittest.TestCase):
         d = getPage(self.introweb_url, method="GET", followRedirect=True)
         def _check(res):
             try:
-                self.failUnless("%s: %s" % (allmydata.__appname__, allmydata.__version__)
-                                in res)
-                self.failUnless("Announcement Summary: storage: 5, stub_client: 5" in res)
+                self.failUnless("%s: %s" % (allmydata.__appname__, allmydata.__version__) in res)
+                verstr = str(allmydata.__version__)
+
+                # The Python "rational version numbering" convention
+                # disallows "-r$REV" but allows ".post$REV"
+                # instead. Eventually we'll probably move to
+                # that. When we do, this test won't go red:
+                ix = verstr.rfind('-r')
+                if ix != -1:
+                    altverstr = verstr[:ix] + '.post' + verstr[ix+2:]
+                else:
+                    ix = verstr.rfind('.post')
+                    if ix != -1:
+                        altverstr = verstr[:ix] + '-r' + verstr[ix+5:]
+                    else:
+                        altverstr = verstr
+
+                appverstr = "%s: %s" % (allmydata.__appname__, verstr)
+                newappverstr = "%s: %s" % (allmydata.__appname__, altverstr)
+
+                self.failUnless((appverstr in res) or (newappverstr in res), (appverstr, newappverstr, res))
+                self.failUnless("Announcement Summary: storage: 5" in res)
                 self.failUnless("Subscription Summary: storage: 5" in res)
+                self.failUnless("tahoe.css" in res)
             except unittest.FailTest:
                 print
                 print "GET %s output was:" % self.introweb_url
                 print res
                 raise
         d.addCallback(_check)
+        # make sure it serves the CSS too
+        d.addCallback(lambda res:
+                      getPage(self.introweb_url+"tahoe.css", method="GET"))
         d.addCallback(lambda res:
                       getPage(self.introweb_url + "?t=json",
                               method="GET", followRedirect=True))
@@ -771,9 +807,9 @@ class SystemTest(SystemTestMixin, unittest.TestCase):
                 self.failUnlessEqual(data["subscription_summary"],
                                      {"storage": 5})
                 self.failUnlessEqual(data["announcement_summary"],
-                                     {"storage": 5, "stub_client": 5})
+                                     {"storage": 5})
                 self.failUnlessEqual(data["announcement_distinct_hosts"],
-                                     {"storage": 1, "stub_client": 1})
+                                     {"storage": 1})
             except unittest.FailTest:
                 print
                 print "GET %s?t=json output was:" % self.introweb_url
@@ -1083,7 +1119,7 @@ class SystemTest(SystemTestMixin, unittest.TestCase):
         d.addCallback(lambda res: getPage(base + public + "/subdir1"))
         def _got_subdir1(page):
             # there ought to be an href for our file
-            self.failUnless(("<td>%d</td>" % len(self.data)) in page)
+            self.failUnlessIn('<td align="right">%d</td>' % len(self.data), page)
             self.failUnless(">mydata567</a>" in page)
         d.addCallback(_got_subdir1)
         d.addCallback(self.log, "done with _got_subdir1")
@@ -1185,8 +1221,12 @@ class SystemTest(SystemTestMixin, unittest.TestCase):
             return self.GET("status/publish-%d" % self._publish_status)
         d.addCallback(_got_update)
         def _got_publish(res):
+            self.failUnlessIn("Publish Results", res)
             return self.GET("status/retrieve-%d" % self._retrieve_status)
         d.addCallback(_got_publish)
+        def _got_retrieve(res):
+            self.failUnlessIn("Retrieve Results", res)
+        d.addCallback(_got_retrieve)
 
         # check that the helper status page exists
         d.addCallback(lambda res:
@@ -1270,7 +1310,7 @@ class SystemTest(SystemTestMixin, unittest.TestCase):
         # exercise some of the diagnostic tools in runner.py
 
         # find a share
-        for (dirpath, dirnames, filenames) in os.walk(self.basedir):
+        for (dirpath, dirnames, filenames) in os.walk(unicode(self.basedir)):
             if "storage" not in dirpath:
                 continue
             if not filenames:
@@ -1369,7 +1409,7 @@ class SystemTest(SystemTestMixin, unittest.TestCase):
             self.failUnlessEqual(data, expected_data)
         d.addCallback(_check)
         d.addCallback(lambda res: rref.callRemote("speed_test", 1, 200, False))
-        if sys.platform == "linux2":
+        if sys.platform in ("linux2", "linux3"):
             d.addCallback(lambda res: rref.callRemote("get_memory_usage"))
         d.addCallback(lambda res: rref.callRemote("measure_peer_response_time"))
         return d
@@ -1489,7 +1529,7 @@ class SystemTest(SystemTestMixin, unittest.TestCase):
         d.addCallback(run, "put", files[1], "subdir/tahoe-file1")
         #  tahoe put bar tahoe:FOO
         d.addCallback(run, "put", files[2], "tahoe:file2")
-        d.addCallback(run, "put", "--mutable", files[3], "tahoe:file3")
+        d.addCallback(run, "put", "--format=SDMF", files[3], "tahoe:file3")
         def _check_put_mutable((out,err)):
             self._mutable_file3_uri = out.strip()
         d.addCallback(_check_put_mutable)
@@ -1714,6 +1754,89 @@ class SystemTest(SystemTestMixin, unittest.TestCase):
 
         return d
 
+    def test_filesystem_with_cli_in_subprocess(self):
+        # We do this in a separate test so that test_filesystem doesn't skip if we can't run bin/tahoe.
+
+        self.basedir = "system/SystemTest/test_filesystem_with_cli_in_subprocess"
+        d = self.set_up_nodes()
+        def _new_happy_semantics(ign):
+            for c in self.clients:
+                c.DEFAULT_ENCODING_PARAMETERS['happy'] = 1
+        d.addCallback(_new_happy_semantics)
+
+        def _run_in_subprocess(ignored, verb, *args, **kwargs):
+            stdin = kwargs.get("stdin")
+            env = kwargs.get("env")
+            newargs = [verb, "--node-directory", self.getdir("client0")] + list(args)
+            return self.run_bintahoe(newargs, stdin=stdin, env=env)
+
+        def _check_succeeded(res, check_stderr=True):
+            out, err, rc_or_sig = res
+            self.failUnlessEqual(rc_or_sig, 0, str(res))
+            if check_stderr:
+                self.failUnlessEqual(err, "")
+
+        d.addCallback(_run_in_subprocess, "create-alias", "newalias")
+        d.addCallback(_check_succeeded)
+
+        STDIN_DATA = "This is the file to upload from stdin."
+        d.addCallback(_run_in_subprocess, "put", "-", "newalias:tahoe-file", stdin=STDIN_DATA)
+        d.addCallback(_check_succeeded, check_stderr=False)
+
+        def _mv_with_http_proxy(ign):
+            env = os.environ
+            env['http_proxy'] = env['HTTP_PROXY'] = "http://127.0.0.0:12345"  # invalid address
+            return _run_in_subprocess(None, "mv", "newalias:tahoe-file", "newalias:tahoe-moved", env=env)
+        d.addCallback(_mv_with_http_proxy)
+        d.addCallback(_check_succeeded)
+
+        d.addCallback(_run_in_subprocess, "ls", "newalias:")
+        def _check_ls(res):
+            out, err, rc_or_sig = res
+            self.failUnlessEqual(rc_or_sig, 0, str(res))
+            self.failUnlessEqual(err, "", str(res))
+            self.failUnlessIn("tahoe-moved", out)
+            self.failIfIn("tahoe-file", out)
+        d.addCallback(_check_ls)
+        return d
+
+    def test_debug_trial(self):
+        def _check_for_line(lines, result, test):
+            for l in lines:
+                if result in l and test in l:
+                    return
+            self.fail("output (prefixed with '##') does not have a line containing both %r and %r:\n## %s"
+                      % (result, test, "\n## ".join(lines)))
+
+        def _check_for_outcome(lines, out, outcome):
+            self.failUnlessIn(outcome, out, "output (prefixed with '##') does not contain %r:\n## %s"
+                                            % (outcome, "\n## ".join(lines)))
+
+        d = self.run_bintahoe(['debug', 'trial', '--reporter=verbose',
+                               'allmydata.test.trialtest'])
+        def _check_failure( (out, err, rc) ):
+            self.failUnlessEqual(rc, 1)
+            lines = out.split('\n')
+            _check_for_line(lines, "[SKIPPED]", "test_skip")
+            _check_for_line(lines, "[TODO]",    "test_todo")
+            _check_for_line(lines, "[FAIL]",    "test_fail")
+            _check_for_line(lines, "[ERROR]",   "test_deferred_error")
+            _check_for_line(lines, "[ERROR]",   "test_error")
+            _check_for_outcome(lines, out, "FAILED")
+        d.addCallback(_check_failure)
+
+        # the --quiet argument regression-tests a problem in finding which arguments to pass to trial
+        d.addCallback(lambda ign: self.run_bintahoe(['--quiet', 'debug', 'trial', '--reporter=verbose',
+                                                     'allmydata.test.trialtest.Success']))
+        def _check_success( (out, err, rc) ):
+            self.failUnlessEqual(rc, 0)
+            lines = out.split('\n')
+            _check_for_line(lines, "[SKIPPED]", "test_skip")
+            _check_for_line(lines, "[TODO]",    "test_todo")
+            _check_for_outcome(lines, out, "PASSED")
+        d.addCallback(_check_success)
+        return d
+
     def _run_cli(self, argv, stdin=""):
         #print "CLI:", argv
         stdout, stderr = StringIO(), StringIO()
@@ -1761,3 +1884,32 @@ class SystemTest(SystemTestMixin, unittest.TestCase):
         d.addCallback(_got_lit_filenode)
         return d
 
+class Connections(SystemTestMixin, unittest.TestCase):
+    def test_rref(self):
+        self.basedir = "system/Connections/rref"
+        d = self.set_up_nodes(2)
+        def _start(ign):
+            self.c0 = self.clients[0]
+            for s in self.c0.storage_broker.get_connected_servers():
+                if "pub-"+s.get_longname() != self.c0.node_key_s:
+                    break
+            self.s1 = s # s1 is the server, not c0
+            self.s1_rref = s.get_rref()
+            self.failIfEqual(self.s1_rref, None)
+            self.failUnless(self.s1.is_connected())
+        d.addCallback(_start)
+
+        # now shut down the server
+        d.addCallback(lambda ign: self.clients[1].disownServiceParent())
+        # and wait for the client to notice
+        def _poll():
+            return len(self.c0.storage_broker.get_connected_servers()) < 2
+        d.addCallback(lambda ign: self.poll(_poll))
+
+        def _down(ign):
+            self.failIf(self.s1.is_connected())
+            rref = self.s1.get_rref()
+            self.failUnless(rref)
+            self.failUnlessIdentical(rref, self.s1_rref)
+        d.addCallback(_down)
+        return d