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:
Itamar Turner-Trauring 2020-11-04 13:44:21 -05:00 committed by GitHub
commit 2c8a91fb6e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 123 additions and 44 deletions

0
newsfragments/3493.minor Normal file
View File

View 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,
)

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -53,6 +53,7 @@ PORTED_MODULES = [
"allmydata.interfaces",
"allmydata.introducer.interfaces",
"allmydata.monitor",
"allmydata.node",
"allmydata.storage.common",
"allmydata.storage.crawler",
"allmydata.storage.expirer",