tahoe-lafs/src/allmydata/test/test_node.py

525 lines
19 KiB
Python
Raw Normal View History

2018-02-07 10:05:21 +00:00
import base64
import os
import stat
import sys
import time
import mock
from twisted.trial import unittest
2007-05-24 00:55:04 +00:00
from twisted.internet import defer
from twisted.python import log
from foolscap.api import flushEventualQueue
import foolscap.logging.log
from twisted.application import service
from allmydata.node import Node, formatTimeTahoeStyle, MissingConfigEntry, read_config, config_from_string
from allmydata.introducer.server import create_introducer
from allmydata.client import create_client, _valid_config_sections
from allmydata.util import fileutil, iputil
from allmydata.util.namespace import Namespace
2018-05-29 23:11:28 +00:00
from allmydata.util.configutil import UnknownConfigError
2010-02-26 08:14:33 +00:00
import allmydata.test.common_util as testutil
class LoggingMultiService(service.MultiService):
def log(self, msg, **kw):
pass
class TestNode(Node):
CERTFILE='DEFAULT_CERTFILE_BLANK'
def __init__(self, basedir):
2018-05-29 21:52:09 +00:00
config = read_config(
basedir,
'DEFAULT_PORTNUMFILE_BLANK',
_valid_config_sections=_valid_config_sections,
)
Node.__init__(self, config, basedir)
class TestCase(testutil.SignalMixin, unittest.TestCase):
2017-08-08 16:42:11 +00:00
@defer.inlineCallbacks
def setUp(self):
testutil.SignalMixin.setUp(self)
self.parent = LoggingMultiService()
self.parent.startService()
2017-08-08 16:42:11 +00:00
self._available_port = yield iputil.allocate_tcp_port()
def tearDown(self):
log.msg("%s.tearDown" % self.__class__.__name__)
testutil.SignalMixin.tearDown(self)
d = defer.succeed(None)
d.addCallback(lambda res: self.parent.stopService())
d.addCallback(flushEventualQueue)
return d
def _test_location(self, basedir, expected_addresses, tub_port=None, tub_location=None, local_addresses=None):
fileutil.make_dirs(basedir)
f = open(os.path.join(basedir, 'tahoe.cfg'), 'wt')
f.write("[node]\n")
if tub_port:
f.write("tub.port = %d\n" % (tub_port,))
if tub_location is not None:
f.write("tub.location = %s\n" % (tub_location,))
f.close()
if local_addresses:
self.patch(iputil, 'get_local_addresses_sync',
lambda: local_addresses)
n = TestNode(basedir)
n.setServiceParent(self.parent)
furl = n.tub.registerReference(n)
for address in expected_addresses:
self.failUnlessIn(address, furl)
def test_location1(self):
return self._test_location(basedir="test_node/test_location1",
expected_addresses=["192.0.2.0:1234"],
tub_location="192.0.2.0:1234")
def test_location2(self):
return self._test_location(basedir="test_node/test_location2",
expected_addresses=["192.0.2.0:1234", "example.org:8091"],
tub_location="192.0.2.0:1234,example.org:8091")
def test_location_not_set(self):
"""Checks the autogenerated furl when tub.location is not set."""
2017-08-08 16:42:11 +00:00
return self._test_location(
basedir="test_node/test_location3",
expected_addresses=[
"127.0.0.1:{}".format(self._available_port),
"192.0.2.0:{}".format(self._available_port),
],
tub_port=self._available_port,
local_addresses=["127.0.0.1", "192.0.2.0"],
)
def test_location_auto_and_explicit(self):
"""Checks the autogenerated furl when tub.location contains 'AUTO'."""
2017-08-08 16:42:11 +00:00
return self._test_location(
basedir="test_node/test_location4",
expected_addresses=[
"127.0.0.1:{}".format(self._available_port),
"192.0.2.0:{}".format(self._available_port),
"example.com:4321",
],
tub_port=self._available_port,
tub_location="AUTO,example.com:{}".format(self._available_port),
local_addresses=["127.0.0.1", "192.0.2.0", "example.com:4321"],
)
def test_tahoe_cfg_utf8(self):
basedir = "test_node/test_tahoe_cfg_utf8"
fileutil.make_dirs(basedir)
f = open(os.path.join(basedir, 'tahoe.cfg'), 'wt')
f.write(u"\uFEFF[node]\n".encode('utf-8'))
f.write(u"nickname = \u2621\n".encode('utf-8'))
f.close()
n = TestNode(basedir)
n.setServiceParent(self.parent)
self.failUnlessEqual(n.get_config("node", "nickname").decode('utf-8'),
u"\u2621")
def test_tahoe_cfg_hash_in_name(self):
basedir = "test_node/test_cfg_hash_in_name"
nickname = "Hash#Bang!" # a clever nickname containing a hash
fileutil.make_dirs(basedir)
f = open(os.path.join(basedir, 'tahoe.cfg'), 'wt')
f.write("[node]\n")
f.write("nickname = %s\n" % (nickname,))
f.close()
n = TestNode(basedir)
self.failUnless(n.nickname == nickname)
def test_private_config(self):
basedir = "test_node/test_private_config"
privdir = os.path.join(basedir, "private")
fileutil.make_dirs(privdir)
f = open(os.path.join(privdir, 'already'), 'wt')
f.write("secret")
f.close()
n = TestNode(basedir)
self.failUnlessEqual(n.get_private_config("already"), "secret")
self.failUnlessEqual(n.get_private_config("not", "default"), "default")
self.failUnlessRaises(MissingConfigEntry, n.get_private_config, "not")
value = n.get_or_create_private_config("new", "start")
self.failUnlessEqual(value, "start")
self.failUnlessEqual(n.get_private_config("new"), "start")
counter = []
def make_newer():
counter.append("called")
return "newer"
value = n.get_or_create_private_config("newer", make_newer)
self.failUnlessEqual(len(counter), 1)
self.failUnlessEqual(value, "newer")
self.failUnlessEqual(n.get_private_config("newer"), "newer")
value = n.get_or_create_private_config("newer", make_newer)
self.failUnlessEqual(len(counter), 1) # don't call unless necessary
self.failUnlessEqual(value, "newer")
def test_timestamp(self):
# this modified logger doesn't seem to get used during the tests,
# probably because we don't modify the LogObserver that trial
# installs (only the one that twistd installs). So manually exercise
# it a little bit.
t = formatTimeTahoeStyle("ignored", time.time())
self.failUnless("Z" in t)
t2 = formatTimeTahoeStyle("ignored", int(time.time()))
self.failUnless("Z" in t2)
def test_secrets_dir(self):
basedir = "test_node/test_secrets_dir"
fileutil.make_dirs(basedir)
n = TestNode(basedir)
self.failUnless(isinstance(n, TestNode))
self.failUnless(os.path.exists(os.path.join(basedir, "private")))
def test_secrets_dir_protected(self):
if "win32" in sys.platform.lower() or "cygwin" in sys.platform.lower():
# We don't know how to test that unprivileged users can't read this
# thing. (Also we don't know exactly how to set the permissions so
# that unprivileged users can't read this thing.)
raise unittest.SkipTest("We don't know how to set permissions on Windows.")
basedir = "test_node/test_secrets_dir_protected"
fileutil.make_dirs(basedir)
n = TestNode(basedir)
self.failUnless(isinstance(n, TestNode))
privdir = os.path.join(basedir, "private")
st = os.stat(privdir)
bits = stat.S_IMODE(st[stat.ST_MODE])
self.failUnless(bits & 0001 == 0, bits)
def test_logdir_is_str(self):
basedir = "test_node/test_logdir_is_str"
fileutil.make_dirs(basedir)
ns = Namespace()
ns.called = False
def call_setLogDir(logdir):
ns.called = True
self.failUnless(isinstance(logdir, str), logdir)
self.patch(foolscap.logging.log, 'setLogDir', call_setLogDir)
TestNode(basedir)
self.failUnless(ns.called)
class EmptyNode(Node):
def __init__(self):
config = config_from_string("", "no portfile")
Node.__init__(self, config, 'no basedir')
EXPECTED = {
# top-level key is tub.port category
"missing": {
# 2nd-level key is tub.location category
"missing": "alloc/auto",
"empty": "ERR2",
"disabled": "ERR4",
"hintstring": "alloc/file",
},
"empty": {
"missing": "ERR1",
"empty": "ERR1",
"disabled": "ERR1",
"hintstring": "ERR1",
},
"disabled": {
"missing": "ERR3",
"empty": "ERR2",
"disabled": "no-listen",
"hintstring": "ERR3",
},
"endpoint": {
"missing": "auto",
"empty": "ERR2",
"disabled": "ERR4",
"hintstring": "manual",
},
}
class PortLocation(unittest.TestCase):
def test_all(self):
for tp in EXPECTED.keys():
for tl in EXPECTED[tp].keys():
exp = EXPECTED[tp][tl]
self._try(tp, tl, exp)
def _try(self, tp, tl, exp):
log.msg("PortLocation._try:", tp, tl, exp)
cfg_tubport = {"missing": None,
"empty": "",
"disabled": "disabled",
"endpoint": "tcp:777",
}[tp]
cfg_location = {"missing": None,
"empty": "",
"disabled": "disabled",
"hintstring": "tcp:HOST:888,AUTO",
}[tl]
n = EmptyNode()
basedir = os.path.join("test_node/portlocation/%s/%s" % (tp, tl))
fileutil.make_dirs(basedir)
2018-05-29 21:52:09 +00:00
config = n.config = read_config(
basedir,
"node.port",
_valid_config_sections=_valid_config_sections,
)
n._reveal_ip = True
if exp in ("ERR1", "ERR2", "ERR3", "ERR4"):
e = self.assertRaises(ValueError, n.get_tub_portlocation,
cfg_tubport, cfg_location)
if exp == "ERR1":
self.assertEqual("tub.port must not be empty", str(e))
elif exp == "ERR2":
self.assertEqual("tub.location must not be empty", str(e))
elif exp == "ERR3":
self.assertEqual("tub.port is disabled, but not tub.location",
str(e))
elif exp == "ERR4":
self.assertEqual("tub.location is disabled, but not tub.port",
str(e))
else:
self.assert_(False)
elif exp == "no-listen":
res = n.get_tub_portlocation(cfg_tubport, cfg_location)
self.assertEqual(res, None)
elif exp in ("alloc/auto", "alloc/file", "auto", "manual"):
with mock.patch("allmydata.util.iputil.get_local_addresses_sync",
return_value=["LOCAL"]):
with mock.patch("allmydata.util.iputil.allocate_tcp_port",
return_value=999):
port, location = n.get_tub_portlocation(cfg_tubport,
cfg_location)
try:
with open(config.portnum_fname, "r") as f:
saved_port = f.read().strip()
except EnvironmentError:
saved_port = None
if exp == "alloc/auto":
self.assertEqual(port, "tcp:999")
self.assertEqual(location, "tcp:LOCAL:999")
self.assertEqual(saved_port, "tcp:999")
elif exp == "alloc/file":
self.assertEqual(port, "tcp:999")
self.assertEqual(location, "tcp:HOST:888,tcp:LOCAL:999")
self.assertEqual(saved_port, "tcp:999")
elif exp == "auto":
self.assertEqual(port, "tcp:777")
self.assertEqual(location, "tcp:LOCAL:777")
self.assertEqual(saved_port, None)
elif exp == "manual":
self.assertEqual(port, "tcp:777")
self.assertEqual(location, "tcp:HOST:888,tcp:LOCAL:777")
self.assertEqual(saved_port, None)
else:
self.assert_(False)
else:
self.assert_(False)
BASE_CONFIG = """
[client]
introducer.furl = empty
[tor]
enabled = false
[i2p]
enabled = false
[node]
"""
NOLISTEN = """
[node]
tub.port = disabled
tub.location = disabled
"""
DISABLE_STORAGE = """
[storage]
enabled = false
"""
ENABLE_STORAGE = """
[storage]
enabled = true
"""
ENABLE_HELPER = """
[helper]
enabled = true
"""
class FakeTub:
def __init__(self):
self.tubID = base64.b32encode("foo")
self.listening_ports = []
def setOption(self, name, value): pass
def removeAllConnectionHintHandlers(self): pass
def addConnectionHintHandler(self, hint_type, handler): pass
def listenOn(self, what):
self.listening_ports.append(what)
def setLocation(self, location): pass
def setServiceParent(self, parent): pass
class Listeners(unittest.TestCase):
def test_multiple_ports(self):
n = EmptyNode()
n.basedir = self.mktemp()
n.config_fname = os.path.join(n.basedir, "tahoe.cfg")
os.mkdir(n.basedir)
os.mkdir(os.path.join(n.basedir, "private"))
port1 = iputil.allocate_tcp_port()
port2 = iputil.allocate_tcp_port()
port = ("tcp:%d:interface=127.0.0.1,tcp:%d:interface=127.0.0.1" %
(port1, port2))
location = "tcp:localhost:%d,tcp:localhost:%d" % (port1, port2)
with open(n.config_fname, "w") as f:
f.write(BASE_CONFIG)
f.write("tub.port = %s\n" % port)
f.write("tub.location = %s\n" % location)
# we're doing a lot of calling-into-setup-methods here, it might be
# better to just create a real Node instance, I'm not sure.
2018-05-29 21:52:09 +00:00
n.config = read_config(
n.basedir,
"client.port",
_valid_config_sections=_valid_config_sections,
)
n.check_privacy()
n.services = []
n.create_i2p_provider()
n.create_tor_provider()
n.init_connections()
n.set_tub_options()
t = FakeTub()
with mock.patch("allmydata.node.Tub", return_value=t):
n.create_main_tub()
self.assertEqual(t.listening_ports,
["tcp:%d:interface=127.0.0.1" % port1,
"tcp:%d:interface=127.0.0.1" % port2])
def test_tor_i2p_listeners(self):
n = EmptyNode()
n.basedir = self.mktemp()
n.config_fname = os.path.join(n.basedir, "tahoe.cfg")
os.mkdir(n.basedir)
os.mkdir(os.path.join(n.basedir, "private"))
with open(n.config_fname, "w") as f:
f.write(BASE_CONFIG)
f.write("tub.port = listen:i2p,listen:tor\n")
f.write("tub.location = tcp:example.org:1234\n")
# we're doing a lot of calling-into-setup-methods here, it might be
# better to just create a real Node instance, I'm not sure.
2018-05-29 21:52:09 +00:00
n.config = read_config(
n.basedir,
"client.port",
_valid_config_sections=_valid_config_sections,
)
n.check_privacy()
n.services = []
i2p_ep = object()
tor_ep = object()
n._i2p_provider = mock.Mock()
n._i2p_provider.get_listener = mock.Mock(return_value=i2p_ep)
n._tor_provider = mock.Mock()
n._tor_provider.get_listener = mock.Mock(return_value=tor_ep)
n.init_connections()
n.set_tub_options()
t = FakeTub()
with mock.patch("allmydata.node.Tub", return_value=t):
n.create_main_tub()
self.assertEqual(n._i2p_provider.get_listener.mock_calls, [mock.call()])
self.assertEqual(n._tor_provider.get_listener.mock_calls, [mock.call()])
self.assertEqual(t.listening_ports, [i2p_ep, tor_ep])
class ClientNotListening(unittest.TestCase):
def test_disabled(self):
basedir = "test_node/test_disabled"
fileutil.make_dirs(basedir)
f = open(os.path.join(basedir, 'tahoe.cfg'), 'wt')
f.write(BASE_CONFIG)
f.write(NOLISTEN)
f.write(DISABLE_STORAGE)
f.close()
n = create_client(basedir)
self.assertEqual(n.tub.getListeners(), [])
def test_disabled_but_storage(self):
basedir = "test_node/test_disabled_but_storage"
fileutil.make_dirs(basedir)
f = open(os.path.join(basedir, 'tahoe.cfg'), 'wt')
f.write(BASE_CONFIG)
f.write(NOLISTEN)
f.write(ENABLE_STORAGE)
f.close()
e = self.assertRaises(ValueError, create_client, basedir)
self.assertIn("storage is enabled, but tub is not listening", str(e))
def test_disabled_but_helper(self):
basedir = "test_node/test_disabled_but_helper"
fileutil.make_dirs(basedir)
f = open(os.path.join(basedir, 'tahoe.cfg'), 'wt')
f.write(BASE_CONFIG)
f.write(NOLISTEN)
f.write(DISABLE_STORAGE)
f.write(ENABLE_HELPER)
f.close()
e = self.assertRaises(ValueError, create_client, basedir)
self.assertIn("helper is enabled, but tub is not listening", str(e))
class IntroducerNotListening(unittest.TestCase):
def test_port_none_introducer(self):
basedir = "test_node/test_port_none_introducer"
fileutil.make_dirs(basedir)
f = open(os.path.join(basedir, 'tahoe.cfg'), 'wt')
f.write("[node]\n")
f.write("tub.port = disabled\n")
f.write("tub.location = disabled\n")
f.close()
e = self.assertRaises(ValueError, create_introducer, basedir)
self.assertIn("we are Introducer, but tub is not listening", str(e))
2018-05-29 23:11:28 +00:00
class Configuration(unittest.TestCase):
def setUp(self):
self.basedir = self.mktemp()
fileutil.make_dirs(self.basedir)
def test_read_invalid_config(self):
with open(os.path.join(self.basedir, 'tahoe.cfg'), 'w') as f:
f.write(
'[invalid section]\n'
'foo = bar\n'
)
with self.assertRaises(UnknownConfigError) as ctx:
read_config(
self.basedir,
"client.port",
_valid_config_sections=_valid_config_sections,
)
self.assertIn(
"invalid section",
str(ctx.exception),
)
def test_create_client_invalid_config(self):
with open(os.path.join(self.basedir, 'tahoe.cfg'), 'w') as f:
f.write(
'[invalid section]\n'
'foo = bar\n'
)
with self.assertRaises(UnknownConfigError) as ctx:
create_client(self.basedir)
self.assertIn(
"invalid section",
str(ctx.exception),
)