mirror of
https://github.com/tahoe-lafs/tahoe-lafs.git
synced 2025-04-08 03:14:21 +00:00
Merge pull request #883 from tahoe-lafs/3493.node-to-python-3
Port allmydata.node to python 3 Fixes ticket:3493
This commit is contained in:
commit
2c8a91fb6e
0
newsfragments/3493.minor
Normal file
0
newsfragments/3493.minor
Normal file
@ -131,7 +131,7 @@ def _valid_config():
|
||||
return cfg.update(_client_config)
|
||||
|
||||
# this is put into README in new node-directories
|
||||
CLIENT_README = """
|
||||
CLIENT_README = u"""
|
||||
This directory contains files which contain private data for the Tahoe node,
|
||||
such as private keys. On Unix-like systems, the permissions on this directory
|
||||
are set to disallow users other than its owner from reading the contents of
|
||||
@ -512,7 +512,7 @@ def create_introducer_clients(config, main_tub, _introducer_factory=None):
|
||||
config.nickname,
|
||||
str(allmydata.__full_version__),
|
||||
str(_Client.OLDEST_SUPPORTED_VERSION),
|
||||
node.get_app_versions(),
|
||||
list(node.get_app_versions()),
|
||||
partial(_sequencer, config),
|
||||
introducer_cache_filepath,
|
||||
)
|
||||
|
@ -1,9 +1,18 @@
|
||||
"""
|
||||
This module contains classes and functions to implement and manage
|
||||
a node for Tahoe-LAFS.
|
||||
|
||||
Ported to Python 3.
|
||||
"""
|
||||
from past.builtins import unicode
|
||||
from six import ensure_str
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from future.utils import PY2
|
||||
if PY2:
|
||||
from future.builtins import filter, map, zip, ascii, chr, hex, input, next, oct, open, pow, round, super, bytes, dict, list, object, range, str, max, min # noqa: F401
|
||||
from six import ensure_str, ensure_text
|
||||
|
||||
import datetime
|
||||
import os.path
|
||||
@ -71,7 +80,7 @@ def _common_valid_config():
|
||||
|
||||
# Add our application versions to the data that Foolscap's LogPublisher
|
||||
# reports.
|
||||
for thing, things_version in get_package_versions().items():
|
||||
for thing, things_version in list(get_package_versions().items()):
|
||||
app_versions.add_version(thing, things_version)
|
||||
|
||||
# group 1 will be addr (dotted quad string), group 3 if any will be portnum (string)
|
||||
@ -97,8 +106,8 @@ def formatTimeTahoeStyle(self, when):
|
||||
"""
|
||||
d = datetime.datetime.utcfromtimestamp(when)
|
||||
if d.microsecond:
|
||||
return d.isoformat(" ")[:-3]+"Z"
|
||||
return d.isoformat(" ") + ".000Z"
|
||||
return d.isoformat(ensure_str(" "))[:-3]+"Z"
|
||||
return d.isoformat(ensure_str(" ")) + ".000Z"
|
||||
|
||||
PRIV_README = """
|
||||
This directory contains files which contain private data for the Tahoe node,
|
||||
@ -152,6 +161,7 @@ def create_node_dir(basedir, readme_text):
|
||||
privdir = os.path.join(basedir, "private")
|
||||
if not os.path.exists(privdir):
|
||||
fileutil.make_dirs(privdir, 0o700)
|
||||
readme_text = ensure_text(readme_text)
|
||||
with open(os.path.join(privdir, 'README'), 'w') as f:
|
||||
f.write(readme_text)
|
||||
|
||||
@ -172,7 +182,7 @@ def read_config(basedir, portnumfile, generated_files=[], _valid_config=None):
|
||||
|
||||
:returns: :class:`allmydata.node._Config` instance
|
||||
"""
|
||||
basedir = abspath_expanduser_unicode(unicode(basedir))
|
||||
basedir = abspath_expanduser_unicode(ensure_text(basedir))
|
||||
if _valid_config is None:
|
||||
_valid_config = _common_valid_config()
|
||||
|
||||
@ -274,16 +284,11 @@ class _Config(object):
|
||||
configparser (might be 'fake' if using in-memory data)
|
||||
"""
|
||||
self.portnum_fname = portnum_fname
|
||||
self._basedir = abspath_expanduser_unicode(unicode(basedir))
|
||||
self._basedir = abspath_expanduser_unicode(ensure_text(basedir))
|
||||
self._config_fname = config_fname
|
||||
self.config = configparser
|
||||
|
||||
nickname_utf8 = self.get_config("node", "nickname", "<unspecified>")
|
||||
if isinstance(nickname_utf8, bytes): # Python 2
|
||||
self.nickname = nickname_utf8.decode("utf-8")
|
||||
else:
|
||||
self.nickname = nickname_utf8
|
||||
assert type(self.nickname) is unicode
|
||||
self.nickname = self.get_config("node", "nickname", u"<unspecified>")
|
||||
assert isinstance(self.nickname, str)
|
||||
|
||||
def validate(self, valid_config_sections):
|
||||
configutil.validate_config(self._config_fname, self.config, valid_config_sections)
|
||||
@ -316,7 +321,7 @@ class _Config(object):
|
||||
return self.config.getboolean(section, option)
|
||||
|
||||
item = self.config.get(section, option)
|
||||
if option.endswith(".furl") and self._contains_unescaped_hash(item):
|
||||
if option.endswith(".furl") and '#' in item:
|
||||
raise UnescapedHashError(section, option, item)
|
||||
|
||||
return item
|
||||
@ -368,8 +373,8 @@ class _Config(object):
|
||||
raise MissingConfigEntry("The required configuration file %s is missing."
|
||||
% (quote_output(privname),))
|
||||
if isinstance(default, bytes):
|
||||
default = unicode(default, "utf-8")
|
||||
if isinstance(default, unicode):
|
||||
default = str(default, "utf-8")
|
||||
if isinstance(default, str):
|
||||
value = default
|
||||
else:
|
||||
value = default()
|
||||
@ -381,7 +386,7 @@ class _Config(object):
|
||||
config file that resides within the subdirectory named 'private'), and
|
||||
return it.
|
||||
"""
|
||||
if isinstance(value, unicode):
|
||||
if isinstance(value, str):
|
||||
value = value.encode("utf-8")
|
||||
privname = os.path.join(self._basedir, "private", name)
|
||||
with open(privname, "wb") as f:
|
||||
@ -423,17 +428,6 @@ class _Config(object):
|
||||
os.path.join(self._basedir, *args)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _contains_unescaped_hash(item):
|
||||
characters = iter(item)
|
||||
for c in characters:
|
||||
if c == '\\':
|
||||
characters.next()
|
||||
elif c == '#':
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def create_tub_options(config):
|
||||
"""
|
||||
@ -536,12 +530,12 @@ def create_tub(tub_options, default_connection_handlers, foolscap_connection_han
|
||||
the new Tub via `Tub.setOption`
|
||||
"""
|
||||
tub = Tub(**kwargs)
|
||||
for (name, value) in tub_options.items():
|
||||
for (name, value) in list(tub_options.items()):
|
||||
tub.setOption(name, value)
|
||||
handlers = default_connection_handlers.copy()
|
||||
handlers.update(handler_overrides)
|
||||
tub.removeAllConnectionHintHandlers()
|
||||
for hint_type, handler_name in handlers.items():
|
||||
for hint_type, handler_name in list(handlers.items()):
|
||||
handler = foolscap_connection_handlers.get(handler_name)
|
||||
if handler:
|
||||
tub.addConnectionHintHandler(hint_type, handler)
|
||||
@ -698,7 +692,7 @@ def create_main_tub(config, tub_options,
|
||||
else:
|
||||
port_or_endpoint = port
|
||||
# Foolscap requires native strings:
|
||||
if isinstance(port_or_endpoint, (bytes, unicode)):
|
||||
if isinstance(port_or_endpoint, (bytes, str)):
|
||||
port_or_endpoint = ensure_str(port_or_endpoint)
|
||||
tub.listenOn(port_or_endpoint)
|
||||
tub.setLocation(location)
|
||||
|
@ -39,7 +39,7 @@ from testtools.twistedsupport import (
|
||||
import allmydata
|
||||
import allmydata.util.log
|
||||
|
||||
from allmydata.node import OldConfigError, UnescapedHashError, _Config, create_node_dir
|
||||
from allmydata.node import OldConfigError, UnescapedHashError, create_node_dir
|
||||
from allmydata.frontends.auth import NeedRootcapLookupScheme
|
||||
from allmydata.version_checks import (
|
||||
get_package_versions_string,
|
||||
@ -112,11 +112,11 @@ class Basic(testutil.ReallyEqualMixin, unittest.TestCase):
|
||||
@defer.inlineCallbacks
|
||||
def test_comment(self):
|
||||
"""
|
||||
An unescaped comment character (#) in a furl results in an
|
||||
A comment character (#) in a furl results in an
|
||||
UnescapedHashError Failure.
|
||||
"""
|
||||
should_fail = [r"test#test", r"#testtest", r"test\\#test"]
|
||||
should_not_fail = [r"test\#test", r"test\\\#test", r"testtest"]
|
||||
should_fail = [r"test#test", r"#testtest", r"test\\#test", r"test\#test",
|
||||
r"test\\\#test"]
|
||||
|
||||
basedir = "test_client.Basic.test_comment"
|
||||
os.mkdir(basedir)
|
||||
@ -127,17 +127,11 @@ class Basic(testutil.ReallyEqualMixin, unittest.TestCase):
|
||||
fileutil.write(os.path.join(basedir, "tahoe.cfg"), config)
|
||||
|
||||
for s in should_fail:
|
||||
self.failUnless(_Config._contains_unescaped_hash(s))
|
||||
write_config(s)
|
||||
with self.assertRaises(UnescapedHashError) as ctx:
|
||||
yield client.create_client(basedir)
|
||||
self.assertIn("[client]introducer.furl", str(ctx.exception))
|
||||
|
||||
for s in should_not_fail:
|
||||
self.failIf(_Config._contains_unescaped_hash(s))
|
||||
write_config(s)
|
||||
yield client.create_client(basedir)
|
||||
|
||||
def test_unreadable_config(self):
|
||||
if sys.platform == "win32":
|
||||
# if somebody knows a clever way to do this (cause
|
||||
|
@ -17,6 +17,7 @@ import sys
|
||||
import time
|
||||
import mock
|
||||
from textwrap import dedent
|
||||
import configparser
|
||||
|
||||
from hypothesis import (
|
||||
given,
|
||||
@ -44,6 +45,7 @@ from allmydata.node import (
|
||||
MissingConfigEntry,
|
||||
_tub_portlocation,
|
||||
formatTimeTahoeStyle,
|
||||
UnescapedHashError,
|
||||
)
|
||||
from allmydata.introducer.server import create_introducer
|
||||
from allmydata import client
|
||||
@ -199,6 +201,37 @@ class TestCase(testutil.SignalMixin, unittest.TestCase):
|
||||
config = read_config(basedir, "")
|
||||
self.failUnless(config.nickname == nickname)
|
||||
|
||||
def test_hash_in_furl(self):
|
||||
"""
|
||||
Hashes in furl options are not allowed, resulting in exception.
|
||||
"""
|
||||
basedir = self.mktemp()
|
||||
fileutil.make_dirs(basedir)
|
||||
with open(os.path.join(basedir, 'tahoe.cfg'), 'wt') as f:
|
||||
f.write("[node]\n")
|
||||
f.write("log_gatherer.furl = lalal#onohash\n")
|
||||
|
||||
config = read_config(basedir, "")
|
||||
with self.assertRaises(UnescapedHashError):
|
||||
config.get_config("node", "log_gatherer.furl")
|
||||
|
||||
def test_missing_config_item(self):
|
||||
"""
|
||||
If a config item is missing:
|
||||
|
||||
1. Given a default, return default.
|
||||
2. Otherwise, raise MissingConfigEntry.
|
||||
"""
|
||||
basedir = self.mktemp()
|
||||
fileutil.make_dirs(basedir)
|
||||
with open(os.path.join(basedir, 'tahoe.cfg'), 'wt') as f:
|
||||
f.write("[node]\n")
|
||||
config = read_config(basedir, "")
|
||||
|
||||
self.assertEquals(config.get_config("node", "log_gatherer.furl", "def"), "def")
|
||||
with self.assertRaises(MissingConfigEntry):
|
||||
config.get_config("node", "log_gatherer.furl")
|
||||
|
||||
def test_config_required(self):
|
||||
"""
|
||||
Asking for missing (but required) configuration is an error
|
||||
@ -231,6 +264,31 @@ class TestCase(testutil.SignalMixin, unittest.TestCase):
|
||||
("timeout.disconnect", "12"),
|
||||
],
|
||||
)
|
||||
self.assertEqual(
|
||||
config.items("node", [("unnecessary", "default")]),
|
||||
[("nickname", "foo"),
|
||||
("timeout.disconnect", "12"),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
def test_config_items_missing_section(self):
|
||||
"""
|
||||
If a default is given for a missing section, the default is used.
|
||||
|
||||
Lacking both default and section, an error is raised.
|
||||
"""
|
||||
basedir = self.mktemp()
|
||||
create_node_dir(basedir, "testing")
|
||||
|
||||
with open(os.path.join(basedir, 'tahoe.cfg'), 'wt') as f:
|
||||
f.write("")
|
||||
|
||||
config = read_config(basedir, "portnum")
|
||||
with self.assertRaises(configparser.NoSectionError):
|
||||
config.items("nosuch")
|
||||
default = [("hello", "world")]
|
||||
self.assertEqual(config.items("nosuch", default), default)
|
||||
|
||||
@skipIf(
|
||||
"win32" in sys.platform.lower() or "cygwin" in sys.platform.lower(),
|
||||
@ -788,3 +846,35 @@ class Configuration(unittest.TestCase):
|
||||
"invalid section",
|
||||
str(ctx.exception),
|
||||
)
|
||||
|
||||
|
||||
|
||||
class FakeProvider(object):
|
||||
"""Emulate Tor and I2P providers."""
|
||||
|
||||
def get_tor_handler(self):
|
||||
return "TORHANDLER!"
|
||||
|
||||
def get_i2p_handler(self):
|
||||
return "I2PHANDLER!"
|
||||
|
||||
|
||||
class CreateConnectionHandlers(unittest.TestCase):
|
||||
"""
|
||||
Tests for create_connection_handlers().
|
||||
"""
|
||||
|
||||
def test_tcp_disabled(self):
|
||||
"""
|
||||
If tcp is set to disabled, no TCP handler is set.
|
||||
"""
|
||||
config = config_from_string("", "", dedent("""
|
||||
[connections]
|
||||
tcp = disabled
|
||||
"""))
|
||||
reactor = object() # it's not actually used?!
|
||||
provider = FakeProvider()
|
||||
default_handlers, _ = create_connection_handlers(
|
||||
reactor, config, provider, provider
|
||||
)
|
||||
self.assertIs(default_handlers["tcp"], None)
|
||||
|
@ -53,6 +53,7 @@ PORTED_MODULES = [
|
||||
"allmydata.interfaces",
|
||||
"allmydata.introducer.interfaces",
|
||||
"allmydata.monitor",
|
||||
"allmydata.node",
|
||||
"allmydata.storage.common",
|
||||
"allmydata.storage.crawler",
|
||||
"allmydata.storage.expirer",
|
||||
|
Loading…
x
Reference in New Issue
Block a user