mirror of
https://github.com/tahoe-lafs/tahoe-lafs.git
synced 2025-01-13 16:29:51 +00:00
f1da68f340
I did an audit of the code base and AFAICT the `node.config_from_string(...)` is only used internally. Much of that usage is in tests where most of the usages feed in non-specific, simple `"..."` string literals (IOW, bytes under py2, unicode under py3) while one test module used `b"..."` byte string literals. Given all that it seems to me that the best goal would be to use simple string literals throughout the usage of `node.config_from_string(...)` and have only one special case in that function to handle the difference between versions. I just discovered that running the test with `TEST_SUITE=allmydata` doesn't run the tests in `allmydata.test.test_node` but running them with `TEST_SUITE=allmydata.test.test_node` does run them. I'm trying to figure out why that is, but in the meantime here are the differences in the Python 3 test output when running just the `allmydata.test.test_node` tests. This changes converts 11 tests from errros to success, changes the specific errors for others and improves coverage a bit: ```diff --- ../../.tox/make-test-py3-all-old.log 2020-10-01 11:56:15.428609940 -0700 +++ ../../.tox/make-test-py3-all-new.log 2020-10-01 11:56:55.052792565 -0700 @@ -95,9 +95,9 @@ tor_provider, File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/introducer/server.py", line 87, in __init__ node.Node.__init__(self, config, main_tub, control_tub, i2p_provider, tor_provider) - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/node.py", line 734, in __init__ + File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/node.py", line 739, in __init__ self.setup_logging() - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/node.py", line 826, in setup_logging + File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/node.py", line 831, in setup_logging newmeth = types.UnboundMethodType(formatTimeTahoeStyle, ob, ob.__class__) builtins.AttributeError: module 'types' has no attribute 'UnboundMethodType' @@ -158,53 +158,29 @@ (#.### secs) allmydata.test.test_node.TestCase.test_config_required ... [OK] (#.### secs) -allmydata.test.test_node.TestCase.test_location1 ... Traceback (most recent call last): - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/test/test_node.py", line 112, in test_location1 - tub_location="192.0.2.0:1234") - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/test/test_node.py", line 99, in _test_location - tub = testing_tub(config_data) - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/test/test_node.py", line 53, in testing_tub - config = config_from_string(basedir, 'DEFAULT_PORTNUMFILE_BLANK', config_data) - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/node.py", line 209, in config_from_string - parser.readfp(BytesIO(config_str)) -builtins.TypeError: a bytes-like object is required, not 'str' -[ERROR] +allmydata.test.test_node.TestCase.test_location1 ... [OK] (#.### secs) allmydata.test.test_node.TestCase.test_location2 ... Traceback (most recent call last): File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/test/test_node.py", line 117, in test_location2 tub_location="192.0.2.0:1234,example.org:8091") File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/test/test_node.py", line 99, in _test_location tub = testing_tub(config_data) - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/test/test_node.py", line 53, in testing_tub - config = config_from_string(basedir, 'DEFAULT_PORTNUMFILE_BLANK', config_data) - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/node.py", line 209, in config_from_string - parser.readfp(BytesIO(config_str)) -builtins.TypeError: a bytes-like object is required, not 'str' + File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/test/test_node.py", line 65, in testing_tub + cert_filename='DEFAULT_CERTFILE_BLANK' + File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/node.py", line 669, in create_main_tub + portlocation = _tub_portlocation(config) + File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/node.py", line 596, in _tub_portlocation + tubport = _convert_tub_port(file_tubport) + File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/node.py", line 552, in _convert_tub_port + if re.search(r'^\d+$', s): + File "/usr/lib/python3.6/re.py", line 182, in search + return _compile(pattern, flags).search(string) +builtins.TypeError: cannot use a string pattern on a bytes-like object [ERROR] (#.### secs) -allmydata.test.test_node.TestCase.test_location_auto_and_explicit ... Traceback (most recent call last): - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/test/test_node.py", line 142, in test_location_auto_and_explicit - local_addresses=["127.0.0.1", "192.0.2.0", "example.com:4321"], - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/test/test_node.py", line 99, in _test_location - tub = testing_tub(config_data) - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/test/test_node.py", line 53, in testing_tub - config = config_from_string(basedir, 'DEFAULT_PORTNUMFILE_BLANK', config_data) - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/node.py", line 209, in config_from_string - parser.readfp(BytesIO(config_str)) -builtins.TypeError: a bytes-like object is required, not 'str' -[ERROR] +allmydata.test.test_node.TestCase.test_location_auto_and_explicit ... [OK] (#.### secs) -allmydata.test.test_node.TestCase.test_location_not_set ... Traceback (most recent call last): - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/test/test_node.py", line 128, in test_location_not_set - local_addresses=["127.0.0.1", "192.0.2.0"], - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/test/test_node.py", line 99, in _test_location - tub = testing_tub(config_data) - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/test/test_node.py", line 53, in testing_tub - config = config_from_string(basedir, 'DEFAULT_PORTNUMFILE_BLANK', config_data) - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/node.py", line 209, in config_from_string - parser.readfp(BytesIO(config_str)) -builtins.TypeError: a bytes-like object is required, not 'str' -[ERROR] +allmydata.test.test_node.TestCase.test_location_not_set ... [OK] (#.### secs) allmydata.test.test_node.TestCase.test_logdir_is_str ... Traceback (most recent call last): File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/test/test_node.py", line 340, in test_logdir_is_str @@ -215,27 +191,31 @@ storage_broker, File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/client.py", line 676, in __init__ node.Node.__init__(self, config, main_tub, control_tub, i2p_provider, tor_provider) - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/node.py", line 734, in __init__ + File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/node.py", line 739, in __init__ self.setup_logging() - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/node.py", line 826, in setup_logging + File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/node.py", line 831, in setup_logging newmeth = types.UnboundMethodType(formatTimeTahoeStyle, ob, ob.__class__) builtins.AttributeError: module 'types' has no attribute 'UnboundMethodType' [ERROR] (#.### secs) allmydata.test.test_node.TestCase.test_private_config ... Traceback (most recent call last): - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/test/test_node.py", line 259, in test_private_config - config = config_from_string(basedir, "", "") - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/node.py", line 209, in config_from_string - parser.readfp(BytesIO(config_str)) -builtins.TypeError: a bytes-like object is required, not 'str' -[ERROR] + File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/test/test_node.py", line 261, in test_private_config + self.assertEqual(config.get_private_config("already"), "secret") + File "/home/rpatterson/src/work/sfu/tahoe-lafs/.tox/py36-coverage/lib/python3.6/site-packages/twisted/trial/_synctest.py", line 434, in assertEqual + super(_Assertions, self).assertEqual(first, second, msg) + File "/usr/lib/python3.6/unittest/case.py", line 829, in assertEqual + assertion_func(first, second, msg=msg) + File "/usr/lib/python3.6/unittest/case.py", line 822, in _baseAssertEqual + raise self.failureException(msg) +twisted.trial.unittest.FailTest: b'secret' != 'secret' +[FAILURE] (#.### secs) allmydata.test.test_node.TestCase.test_private_config_missing ... [OK] (#.### secs) allmydata.test.test_node.TestCase.test_private_config_unreadable ... Traceback (most recent call last): File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/test/test_node.py", line 213, in test_private_config_unreadable config.get_or_create_private_config("foo", "contents") - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/node.py", line 369, in get_or_create_private_config + File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/node.py", line 374, in get_or_create_private_config fileutil.write(privname, value) File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/util/fileutil.py", line 275, in write f.write(data) @@ -258,77 +238,33 @@ (#.### secs) allmydata.test.test_node.TestCase.test_timestamp ... [OK] (#.### secs) -allmydata.test.test_node.TestCase.test_write_config_unwritable_file ... Traceback (most recent call last): - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/test/test_node.py", line 288, in test_write_config_unwritable_file - config = config_from_string(basedir, "", "") - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/node.py", line 209, in config_from_string - parser.readfp(BytesIO(config_str)) -builtins.TypeError: a bytes-like object is required, not 'str' -[ERROR] +allmydata.test.test_node.TestCase.test_write_config_unwritable_file ... [OK] (#.### secs) -allmydata.test.test_node.TestMissingPorts.test_disabled_port_not_tub ... Traceback (most recent call last): - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/test/test_node.py", line 488, in test_disabled_port_not_tub - config = config_from_string(self.basedir, "portnum", config_data) - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/node.py", line 209, in config_from_string - parser.readfp(BytesIO(config_str)) -builtins.TypeError: a bytes-like object is required, not 'str' -[ERROR] +allmydata.test.test_node.TestMissingPorts.test_disabled_port_not_tub ... [OK] (#.### secs) -allmydata.test.test_node.TestMissingPorts.test_disabled_tub_not_port ... Traceback (most recent call last): - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/test/test_node.py", line 506, in test_disabled_tub_not_port - config = config_from_string(self.basedir, "portnum", config_data) - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/node.py", line 209, in config_from_string - parser.readfp(BytesIO(config_str)) -builtins.TypeError: a bytes-like object is required, not 'str' -[ERROR] +allmydata.test.test_node.TestMissingPorts.test_disabled_tub_not_port ... [OK] (#.### secs) -allmydata.test.test_node.TestMissingPorts.test_empty_tub_location ... Traceback (most recent call last): - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/test/test_node.py", line 470, in test_empty_tub_location - config = config_from_string(self.basedir, "portnum", config_data) - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/node.py", line 209, in config_from_string - parser.readfp(BytesIO(config_str)) -builtins.TypeError: a bytes-like object is required, not 'str' -[ERROR] +allmydata.test.test_node.TestMissingPorts.test_empty_tub_location ... [OK] (#.### secs) -allmydata.test.test_node.TestMissingPorts.test_empty_tub_port ... Traceback (most recent call last): - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/test/test_node.py", line 453, in test_empty_tub_port - config = config_from_string(self.basedir, "portnum", config_data) - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/node.py", line 209, in config_from_string - parser.readfp(BytesIO(config_str)) -builtins.TypeError: a bytes-like object is required, not 'str' -[ERROR] +allmydata.test.test_node.TestMissingPorts.test_empty_tub_port ... [OK] (#.### secs) -allmydata.test.test_node.TestMissingPorts.test_parsing_all_disabled ... Traceback (most recent call last): - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/test/test_node.py", line 439, in test_parsing_all_disabled - config = config_from_string(self.basedir, "portnum", config_data) - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/node.py", line 209, in config_from_string - parser.readfp(BytesIO(config_str)) -builtins.TypeError: a bytes-like object is required, not 'str' -[ERROR] +allmydata.test.test_node.TestMissingPorts.test_parsing_all_disabled ... [OK] (#.### secs) -allmydata.test.test_node.TestMissingPorts.test_parsing_defaults ... Traceback (most recent call last): - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/test/test_node.py", line 392, in test_parsing_defaults - config = config_from_string(self.basedir, "portnum", config_data) - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/node.py", line 209, in config_from_string - parser.readfp(BytesIO(config_str)) -builtins.TypeError: a bytes-like object is required, not 'str' -[ERROR] +allmydata.test.test_node.TestMissingPorts.test_parsing_defaults ... [OK] (#.### secs) allmydata.test.test_node.TestMissingPorts.test_parsing_location_complex ... Traceback (most recent call last): - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/test/test_node.py", line 415, in test_parsing_location_complex - config = config_from_string(self.basedir, "portnum", config_data) - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/node.py", line 209, in config_from_string - parser.readfp(BytesIO(config_str)) -builtins.TypeError: a bytes-like object is required, not 'str' + File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/test/test_node.py", line 418, in test_parsing_location_complex + tubport, tublocation = _tub_portlocation(config) + File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/node.py", line 596, in _tub_portlocation + tubport = _convert_tub_port(file_tubport) + File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/node.py", line 552, in _convert_tub_port + if re.search(r'^\d+$', s): + File "/usr/lib/python3.6/re.py", line 182, in search + return _compile(pattern, flags).search(string) +builtins.TypeError: cannot use a string pattern on a bytes-like object [ERROR] (#.### secs) -allmydata.test.test_node.TestMissingPorts.test_parsing_tcp ... Traceback (most recent call last): - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/test/test_node.py", line 370, in test_parsing_tcp - config = config_from_string(self.basedir, "portnum", config_data) - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/node.py", line 209, in config_from_string - parser.readfp(BytesIO(config_str)) -builtins.TypeError: a bytes-like object is required, not 'str' -[ERROR] +allmydata.test.test_node.TestMissingPorts.test_parsing_tcp ... [OK] (#.### secs) =============================================================================== @@ -415,9 +351,9 @@ tor_provider, File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/introducer/server.py", line 87, in __init__ node.Node.__init__(self, config, main_tub, control_tub, i2p_provider, tor_provider) - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/node.py", line 734, in __init__ + File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/node.py", line 739, in __init__ self.setup_logging() - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/node.py", line 826, in setup_logging + File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/node.py", line 831, in setup_logging newmeth = types.UnboundMethodType(formatTimeTahoeStyle, ob, ob.__class__) builtins.AttributeError: module 'types' has no attribute 'UnboundMethodType' @@ -449,6 +385,20 @@ allmydata.test.test_node.TestCase.test_config_items =============================================================================== +[FAIL] +Traceback (most recent call last): + File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/test/test_node.py", line 261, in test_private_config + self.assertEqual(config.get_private_config("already"), "secret") + File "/home/rpatterson/src/work/sfu/tahoe-lafs/.tox/py36-coverage/lib/python3.6/site-packages/twisted/trial/_synctest.py", line 434, in assertEqual + super(_Assertions, self).assertEqual(first, second, msg) + File "/usr/lib/python3.6/unittest/case.py", line 829, in assertEqual + assertion_func(first, second, msg=msg) + File "/usr/lib/python3.6/unittest/case.py", line 822, in _baseAssertEqual + raise self.failureException(msg) +twisted.trial.unittest.FailTest: b'secret' != 'secret' + +allmydata.test.test_node.TestCase.test_private_config +=============================================================================== [ERROR] Traceback (most recent call last): File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/test/test_node.py", line 657, in test_disabled @@ -503,62 +453,26 @@ =============================================================================== [ERROR] Traceback (most recent call last): - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/test/test_node.py", line 112, in test_location1 - tub_location="192.0.2.0:1234") - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/test/test_node.py", line 99, in _test_location - tub = testing_tub(config_data) - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/test/test_node.py", line 53, in testing_tub - config = config_from_string(basedir, 'DEFAULT_PORTNUMFILE_BLANK', config_data) - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/node.py", line 209, in config_from_string - parser.readfp(BytesIO(config_str)) -builtins.TypeError: a bytes-like object is required, not 'str' - -allmydata.test.test_node.TestCase.test_location1 -=============================================================================== -[ERROR] -Traceback (most recent call last): File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/test/test_node.py", line 117, in test_location2 tub_location="192.0.2.0:1234,example.org:8091") File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/test/test_node.py", line 99, in _test_location tub = testing_tub(config_data) - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/test/test_node.py", line 53, in testing_tub - config = config_from_string(basedir, 'DEFAULT_PORTNUMFILE_BLANK', config_data) - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/node.py", line 209, in config_from_string - parser.readfp(BytesIO(config_str)) -builtins.TypeError: a bytes-like object is required, not 'str' + File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/test/test_node.py", line 65, in testing_tub + cert_filename='DEFAULT_CERTFILE_BLANK' + File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/node.py", line 669, in create_main_tub + portlocation = _tub_portlocation(config) + File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/node.py", line 596, in _tub_portlocation + tubport = _convert_tub_port(file_tubport) + File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/node.py", line 552, in _convert_tub_port + if re.search(r'^\d+$', s): + File "/usr/lib/python3.6/re.py", line 182, in search + return _compile(pattern, flags).search(string) +builtins.TypeError: cannot use a string pattern on a bytes-like object allmydata.test.test_node.TestCase.test_location2 =============================================================================== [ERROR] Traceback (most recent call last): - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/test/test_node.py", line 142, in test_location_auto_and_explicit - local_addresses=["127.0.0.1", "192.0.2.0", "example.com:4321"], - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/test/test_node.py", line 99, in _test_location - tub = testing_tub(config_data) - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/test/test_node.py", line 53, in testing_tub - config = config_from_string(basedir, 'DEFAULT_PORTNUMFILE_BLANK', config_data) - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/node.py", line 209, in config_from_string - parser.readfp(BytesIO(config_str)) -builtins.TypeError: a bytes-like object is required, not 'str' - -allmydata.test.test_node.TestCase.test_location_auto_and_explicit -=============================================================================== -[ERROR] -Traceback (most recent call last): - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/test/test_node.py", line 128, in test_location_not_set - local_addresses=["127.0.0.1", "192.0.2.0"], - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/test/test_node.py", line 99, in _test_location - tub = testing_tub(config_data) - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/test/test_node.py", line 53, in testing_tub - config = config_from_string(basedir, 'DEFAULT_PORTNUMFILE_BLANK', config_data) - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/node.py", line 209, in config_from_string - parser.readfp(BytesIO(config_str)) -builtins.TypeError: a bytes-like object is required, not 'str' - -allmydata.test.test_node.TestCase.test_location_not_set -=============================================================================== -[ERROR] -Traceback (most recent call last): File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/test/test_node.py", line 340, in test_logdir_is_str yield client.create_client(basedir) File "/home/rpatterson/src/work/sfu/tahoe-lafs/.tox/py36-coverage/lib/python3.6/site-packages/twisted/internet/defer.py", line 1418, in _inlineCallbacks @@ -567,9 +481,9 @@ storage_broker, File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/client.py", line 676, in __init__ node.Node.__init__(self, config, main_tub, control_tub, i2p_provider, tor_provider) - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/node.py", line 734, in __init__ + File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/node.py", line 739, in __init__ self.setup_logging() - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/node.py", line 826, in setup_logging + File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/node.py", line 831, in setup_logging newmeth = types.UnboundMethodType(formatTimeTahoeStyle, ob, ob.__class__) builtins.AttributeError: module 'types' has no attribute 'UnboundMethodType' @@ -577,19 +491,9 @@ =============================================================================== [ERROR] Traceback (most recent call last): - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/test/test_node.py", line 259, in test_private_config - config = config_from_string(basedir, "", "") - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/node.py", line 209, in config_from_string - parser.readfp(BytesIO(config_str)) -builtins.TypeError: a bytes-like object is required, not 'str' - -allmydata.test.test_node.TestCase.test_private_config -=============================================================================== -[ERROR] -Traceback (most recent call last): File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/test/test_node.py", line 213, in test_private_config_unreadable config.get_or_create_private_config("foo", "contents") - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/node.py", line 369, in get_or_create_private_config + File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/node.py", line 374, in get_or_create_private_config fileutil.write(privname, value) File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/util/fileutil.py", line 275, in write f.write(data) @@ -607,97 +511,21 @@ =============================================================================== [ERROR] Traceback (most recent call last): - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/test/test_node.py", line 288, in test_write_config_unwritable_file - config = config_from_string(basedir, "", "") - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/node.py", line 209, in config_from_string - parser.readfp(BytesIO(config_str)) -builtins.TypeError: a bytes-like object is required, not 'str' - -allmydata.test.test_node.TestCase.test_write_config_unwritable_file -=============================================================================== -[ERROR] -Traceback (most recent call last): - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/test/test_node.py", line 488, in test_disabled_port_not_tub - config = config_from_string(self.basedir, "portnum", config_data) - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/node.py", line 209, in config_from_string - parser.readfp(BytesIO(config_str)) -builtins.TypeError: a bytes-like object is required, not 'str' - -allmydata.test.test_node.TestMissingPorts.test_disabled_port_not_tub -=============================================================================== -[ERROR] -Traceback (most recent call last): - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/test/test_node.py", line 506, in test_disabled_tub_not_port - config = config_from_string(self.basedir, "portnum", config_data) - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/node.py", line 209, in config_from_string - parser.readfp(BytesIO(config_str)) -builtins.TypeError: a bytes-like object is required, not 'str' - -allmydata.test.test_node.TestMissingPorts.test_disabled_tub_not_port -=============================================================================== -[ERROR] -Traceback (most recent call last): - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/test/test_node.py", line 470, in test_empty_tub_location - config = config_from_string(self.basedir, "portnum", config_data) - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/node.py", line 209, in config_from_string - parser.readfp(BytesIO(config_str)) -builtins.TypeError: a bytes-like object is required, not 'str' - -allmydata.test.test_node.TestMissingPorts.test_empty_tub_location -=============================================================================== -[ERROR] -Traceback (most recent call last): - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/test/test_node.py", line 453, in test_empty_tub_port - config = config_from_string(self.basedir, "portnum", config_data) - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/node.py", line 209, in config_from_string - parser.readfp(BytesIO(config_str)) -builtins.TypeError: a bytes-like object is required, not 'str' - -allmydata.test.test_node.TestMissingPorts.test_empty_tub_port -=============================================================================== -[ERROR] -Traceback (most recent call last): - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/test/test_node.py", line 439, in test_parsing_all_disabled - config = config_from_string(self.basedir, "portnum", config_data) - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/node.py", line 209, in config_from_string - parser.readfp(BytesIO(config_str)) -builtins.TypeError: a bytes-like object is required, not 'str' - -allmydata.test.test_node.TestMissingPorts.test_parsing_all_disabled -=============================================================================== -[ERROR] -Traceback (most recent call last): - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/test/test_node.py", line 392, in test_parsing_defaults - config = config_from_string(self.basedir, "portnum", config_data) - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/node.py", line 209, in config_from_string - parser.readfp(BytesIO(config_str)) -builtins.TypeError: a bytes-like object is required, not 'str' - -allmydata.test.test_node.TestMissingPorts.test_parsing_defaults -=============================================================================== -[ERROR] -Traceback (most recent call last): - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/test/test_node.py", line 415, in test_parsing_location_complex - config = config_from_string(self.basedir, "portnum", config_data) - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/node.py", line 209, in config_from_string - parser.readfp(BytesIO(config_str)) -builtins.TypeError: a bytes-like object is required, not 'str' + File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/test/test_node.py", line 418, in test_parsing_location_complex + tubport, tublocation = _tub_portlocation(config) + File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/node.py", line 596, in _tub_portlocation + tubport = _convert_tub_port(file_tubport) + File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/node.py", line 552, in _convert_tub_port + if re.search(r'^\d+$', s): + File "/usr/lib/python3.6/re.py", line 182, in search + return _compile(pattern, flags).search(string) +builtins.TypeError: cannot use a string pattern on a bytes-like object allmydata.test.test_node.TestMissingPorts.test_parsing_location_complex -=============================================================================== -[ERROR] -Traceback (most recent call last): - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/test/test_node.py", line 370, in test_parsing_tcp - config = config_from_string(self.basedir, "portnum", config_data) - File "/home/rpatterson/src/work/sfu/tahoe-lafs/src/allmydata/node.py", line 209, in config_from_string - parser.readfp(BytesIO(config_str)) -builtins.TypeError: a bytes-like object is required, not 'str' - -allmydata.test.test_node.TestMissingPorts.test_parsing_tcp ------------------------------------------------------------------------------- -Ran 34 tests in 2.788s +Ran 34 tests in 2.516s -FAILED (failures=4, errors=21, successes=9) +FAILED (failures=5, errors=9, successes=20) Name Stmts Miss Branch BrPart Cover Missing ------------------------------------------------------------------------------------------------ src/allmydata/__init__.py 16 4 0 0 75% 18-22, 28-32 @@ -751,7 +579,7 @@ src/allmydata/mutable/repairer.py 57 37 18 0 29% 13, 15, 17, 19, 29-34, 65-71, 74-126, 129-131 src/allmydata/mutable/retrieve.py 489 411 120 0 13% 29-43, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 67-69, 71, 73, 75, 77, 79, 81, 83, 85, 87, 89-90, 105-160, 164, 167-171, 174-175, 186-193, 201-208, 211-212, 223-227, 230-232, 236-254, 257-275, 278-283, 286-332, 344-354, 362-454, 485-516, 529-540, 564-578, 586-597, 607-633, 643-663, 671-699, 712-729, 738-798, 806-829, 839-889, 897-905, 909-910, 919-941, 950-971, 981-994, 999-1005 src/allmydata/mutable/servermap.py 623 524 198 0 12% 26-38, 41-42, 45, 47, 49, 51, 53, 55, 57, 59, 61, 63, 65, 67, 70, 72, 74, 76, 78, 80, 82, 116-124, 130-139, 142, 145, 148, 159-161, 165, 170-172, 175, 177, 180-181, 183, 186-199, 202, 206, 213, 217-220, 225-228, 231, 234-238, 243-252, 255-259, 263-265, 269-275, 280-290, 295-305, 311-315, 320-322, 328-350, 358-363, 370-372, 379, 390-450, 454, 457-461, 466-545, 549-557, 560-575, 578-593, 596-613, 623-638, 642-779, 787, 791-799, 803-804, 816-880, 883-904, 910-914, 919-920, 928-944, 960-974, 981-998, 1002-1012, 1020-1183, 1186-1205, 1209-1225, 1228-1229 -src/allmydata/node.py 388 106 146 39 69% 120, 132, 190, 211-213, 241, 243-245, 278, 284, 291-295, 303-306, 315, 320, 339, 341, 361, 368, 370, 377-379, 393-396, 422, 424, 449, 453, 490, 493, 500, 511-512, 547-549, 566, 574, 581, 583, 590-591, 597, 601, 612, 622-634, 679, 681, 736-750, 756, 764, 792-805, 808-809, 814-815, 827-846, 189->190, 204->208, 240->241, 242->243, 277->278, 314->315, 319->320, 338->339, 340->341, 360->361, 365->368, 391->393, 421->422, 423->424, 448->449, 451->453, 489->490, 492->493, 499->500, 510->511, 565->566, 567->570, 573->574, 575->578, 580->581, 582->583, 585->597, 589->590, 600->601, 603->606, 610->616, 611->612, 618->622, 673->679, 680->681, 763->764, 766->768, 821->830, 823->821 +src/allmydata/node.py 391 87 148 30 75% 20, 125, 137, 195, 246, 248-250, 283, 289, 308-311, 320, 325, 344, 346, 366, 373, 375, 382-384, 398-401, 427, 429, 454, 458, 495, 498, 505, 516-517, 606, 617, 634-638, 684, 686, 741-755, 761, 769, 797-810, 813-814, 819-820, 832-851, 19->20, 194->195, 209->213, 245->246, 247->248, 282->283, 319->320, 324->325, 343->344, 345->346, 365->366, 370->373, 396->398, 426->427, 428->429, 453->454, 456->458, 494->495, 497->498, 504->505, 515->516, 605->606, 616->617, 627->634, 678->684, 685->686, 768->769, 771->773, 826->835, 828->826 src/allmydata/nodemaker.py 97 71 38 0 21% 23-33, 36, 38, 41, 44-47, 49, 53-95, 98-115, 118-125, 129-138, 141-150 src/allmydata/scripts/admin.py 51 31 2 0 38% 9-14, 17-21, 25, 28, 31-37, 40-46, 56-57, 59, 61-66, 74-78 src/allmydata/scripts/backupdb.py 146 146 14 0 0% 1-341 @@ -810,7 +638,7 @@ src/allmydata/util/dictutil.py 38 22 12 1 34% 16, 21-24, 27-31, 34-38, 55-56, 59-60, 63-64, 71, 77-78, 12->16 src/allmydata/util/eliotutil.py 115 68 24 0 35% 82-85, 91-94, 104, 117-122, 129-139, 151, 155-159, 163-167, 179-186, 198-199, 202-210, 222-226, 231-247, 250, 266-294, 308-312 src/allmydata/util/encodingutil.py 217 123 80 12 36% 18, 37-38, 41, 43, 52-53, 69, 75-78, 102, 108, 114-122, 130-134, 145-155, 164, 173-175, 178-181, 187, 196-213, 217-231, 237-243, 279-282, 291-296, 314, 320-322, 327, 334-340, 343-355, 358-363, 366-367, 370-373, 379, 395-405, 412-420, 423, 429, 16->18, 36->37, 40->41, 42->43, 66->69, 72->74, 74->75, 278->279, 285->295, 288->291, 299->310, 319->320 -src/allmydata/util/fileutil.py 343 244 120 13 25% 15, 23-25, 47-55, 71-85, 96-97, 100, 103, 106, 109, 115-116, 119-125, 128, 131, 134, 137-138, 142-145, 151-153, 158, 166-176, 179-184, 201-203, 214-237, 241-244, 247-254, 262, 279, 282-290, 293-304, 326, 328, 336-342, 348, 351, 358, 366-376, 382-400, 405, 410-426, 434-462, 486-529, 548-554, 566-568, 573-604, 608-612, 615-627, 633, 636-659, 13->15, 22->23, 200->201, 259->262, 325->326, 327->328, 332->336, 345->351, 347->348, 357->358, 380->382, 404->405, 571->573 +src/allmydata/util/fileutil.py 343 243 120 13 25% 15, 23-25, 47-55, 71-85, 96-97, 100, 103, 106, 109, 115-116, 119-125, 128, 131, 134, 137-138, 142-145, 151-153, 158, 166-176, 179-184, 201-203, 214-237, 241-244, 247-254, 262, 282-290, 293-304, 326, 328, 336-342, 348, 351, 358, 366-376, 382-400, 405, 410-426, 434-462, 486-529, 548-554, 566-568, 573-604, 608-612, 615-627, 633, 636-659, 13->15, 22->23, 200->201, 259->262, 325->326, 327->328, 332->336, 345->351, 347->348, 357->358, 380->382, 404->405, 571->573 src/allmydata/util/gcutil.py 23 3 8 3 81% 20, 51-57, 19->20, 50->51, 64->exit src/allmydata/util/happinessutil.py 77 62 42 1 13% 15, 25-54, 64-69, 82-92, 142-183, 207-223, 235-249, 13->15 src/allmydata/util/hashutil.py 157 76 8 1 50% 14, 40-42, 45-46, 49-56, 60-62, 66-68, 72-76, 118, 122, 126, 130, 134, 138, 142, 146, 150, 154, 158, 162, 166, 174-176, 180-183, 187, 191, 195, 199, 204, 209-210, 214-215, 219, 223-228, 232, 236, 240, 244, 248-250, 254, 258, 262, 266, 270-271, 278, 282, 12->14 @@ -818,7 +646,7 @@ src/allmydata/util/i2p_provider.py 121 73 36 5 35% 44-67, 72-81, 85-135, 151-161, 168, 176-180, 183-184, 187, 193-216, 219, 226, 167->168, 175->176, 182->183, 186->187, 192->193 src/allmydata/util/idlib.py 5 2 0 0 60% 6, 9 src/allmydata/util/iputil.py 172 74 56 12 52% 14, 63-102, 123-140, 151-184, 209, 216, 229, 237-238, 242, 246, 254-257, 271-277, 328-329, 353-354, 13->14, 215->216, 220->242, 226->229, 234->220, 239->234, 245->246, 249->259, 265->261, 291->329, 295->328, 360->exit -src/allmydata/util/log.py 52 27 16 1 38% 13, 38-41, 46-48, 51-61, 67-75, 78, 12->13 +src/allmydata/util/log.py 52 23 16 2 46% 13, 46-48, 51-61, 67-75, 78, 12->13, 39->41 src/allmydata/util/mathutil.py 12 3 2 1 71% 16, 25-26, 15->16 src/allmydata/util/netstring.py 35 24 12 1 26% 13, 31-54, 12->13 src/allmydata/util/observer.py 91 56 20 1 32% 14, 29-32, 36-38, 41, 44, 47, 50-54, 57-60, 63-66, 69-70, 79, 82, 93-97, 103, 106, 109, 112-113, 119-121, 134, 137-139, 142-145, 148-151, 154-157, 13->14 @@ -854,7 +682,7 @@ src/allmydata/windows/fixups.py 133 133 54 0 0% 1-237 src/allmydata/windows/registry.py 42 42 12 0 0% 1-77 ------------------------------------------------------------------------------------------------ -TOTAL 27467 22018 8248 184 17% +TOTAL 27470 21994 8250 176 17% 12 files skipped due to complete coverage. make[#]: Leaving directory '/home/rpatterson/src/work/sfu/tahoe-lafs' ```
855 lines
31 KiB
Python
855 lines
31 KiB
Python
"""
|
|
This module contains classes and functions to implement and manage
|
|
a node for Tahoe-LAFS.
|
|
"""
|
|
from past.builtins import unicode
|
|
|
|
import datetime
|
|
import os.path
|
|
import re
|
|
import types
|
|
import errno
|
|
from io import StringIO
|
|
import tempfile
|
|
from base64 import b32decode, b32encode
|
|
|
|
# BBB: Python 2 compatibility
|
|
from six.moves import configparser
|
|
from future.utils import PY2
|
|
if PY2:
|
|
from io import BytesIO as StringIO # noqa: F811
|
|
|
|
from twisted.python import log as twlog
|
|
from twisted.application import service
|
|
from twisted.python.failure import Failure
|
|
from foolscap.api import Tub, app_versions
|
|
import foolscap.logging.log
|
|
from allmydata.version_checks import get_package_versions, get_package_versions_string
|
|
from allmydata.util import log
|
|
from allmydata.util import fileutil, iputil
|
|
from allmydata.util.assertutil import _assert
|
|
from allmydata.util.fileutil import abspath_expanduser_unicode
|
|
from allmydata.util.encodingutil import get_filesystem_encoding, quote_output
|
|
from allmydata.util import configutil
|
|
|
|
def _common_valid_config():
|
|
return configutil.ValidConfiguration({
|
|
"connections": (
|
|
"tcp",
|
|
),
|
|
"node": (
|
|
"log_gatherer.furl",
|
|
"nickname",
|
|
"reveal-ip-address",
|
|
"tempdir",
|
|
"timeout.disconnect",
|
|
"timeout.keepalive",
|
|
"tub.location",
|
|
"tub.port",
|
|
"web.port",
|
|
"web.static",
|
|
),
|
|
"i2p": (
|
|
"enabled",
|
|
"i2p.configdir",
|
|
"i2p.executable",
|
|
"launch",
|
|
"sam.port",
|
|
"dest",
|
|
"dest.port",
|
|
"dest.private_key_file",
|
|
),
|
|
"tor": (
|
|
"control.port",
|
|
"enabled",
|
|
"launch",
|
|
"socks.port",
|
|
"tor.executable",
|
|
"onion",
|
|
"onion.local_port",
|
|
"onion.external_port",
|
|
"onion.private_key_file",
|
|
),
|
|
})
|
|
|
|
# Add our application versions to the data that Foolscap's LogPublisher
|
|
# reports.
|
|
for thing, things_version in 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)
|
|
ADDR_RE = re.compile("^([1-9][0-9]*\.[1-9][0-9]*\.[1-9][0-9]*\.[1-9][0-9]*)(:([1-9][0-9]*))?$")
|
|
|
|
# this is put into README in new node-directories (for client and introducers)
|
|
PRIV_README = """
|
|
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
|
|
the files. See the 'configuration.rst' documentation file for details.
|
|
"""
|
|
|
|
|
|
def formatTimeTahoeStyle(self, when):
|
|
"""
|
|
Format the given (UTC) timestamp in the way Tahoe-LAFS expects it,
|
|
for example: 2007-10-12 00:26:28.566Z
|
|
|
|
:param when: UTC POSIX timestamp
|
|
:type when: float
|
|
:returns: datetime.datetime
|
|
"""
|
|
d = datetime.datetime.utcfromtimestamp(when)
|
|
if d.microsecond:
|
|
return d.isoformat(" ")[:-3]+"Z"
|
|
return d.isoformat(" ") + ".000Z"
|
|
|
|
PRIV_README = """
|
|
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
|
|
the files. See the 'configuration.rst' documentation file for details."""
|
|
|
|
class _None(object):
|
|
"""
|
|
This class is to be used as a marker in get_config()
|
|
"""
|
|
pass
|
|
|
|
class MissingConfigEntry(Exception):
|
|
""" A required config entry was not found. """
|
|
|
|
class OldConfigError(Exception):
|
|
""" An obsolete config file was found. See
|
|
docs/historical/configuration.rst. """
|
|
def __str__(self):
|
|
return ("Found pre-Tahoe-LAFS-v1.3 configuration file(s):\n"
|
|
"%s\n"
|
|
"See docs/historical/configuration.rst."
|
|
% "\n".join([quote_output(fname) for fname in self.args[0]]))
|
|
|
|
class OldConfigOptionError(Exception):
|
|
"""Indicate that outdated configuration options are being used."""
|
|
pass
|
|
|
|
class UnescapedHashError(Exception):
|
|
"""Indicate that a configuration entry contains an unescaped '#' character."""
|
|
def __str__(self):
|
|
return ("The configuration entry %s contained an unescaped '#' character."
|
|
% quote_output("[%s]%s = %s" % self.args))
|
|
|
|
class PrivacyError(Exception):
|
|
"""reveal-IP-address = false, but the node is configured in such a way
|
|
that the IP address could be revealed"""
|
|
|
|
|
|
def create_node_dir(basedir, readme_text):
|
|
"""
|
|
Create new new 'node directory' at 'basedir'. This includes a
|
|
'private' subdirectory. If basedir (and privdir) already exists,
|
|
nothing is done.
|
|
|
|
:param readme_text: text to put in <basedir>/private/README
|
|
"""
|
|
if not os.path.exists(basedir):
|
|
fileutil.make_dirs(basedir)
|
|
privdir = os.path.join(basedir, "private")
|
|
if not os.path.exists(privdir):
|
|
fileutil.make_dirs(privdir, 0o700)
|
|
with open(os.path.join(privdir, 'README'), 'w') as f:
|
|
f.write(readme_text)
|
|
|
|
|
|
def read_config(basedir, portnumfile, generated_files=[], _valid_config=None):
|
|
"""
|
|
Read and validate configuration.
|
|
|
|
:param unicode basedir: directory where configuration data begins
|
|
|
|
:param unicode portnumfile: filename fragment for "port number" files
|
|
|
|
:param list generated_files: a list of automatically-generated
|
|
configuration files.
|
|
|
|
:param ValidConfiguration _valid_config: (internal use, optional) a
|
|
structure defining valid configuration sections and keys
|
|
|
|
:returns: :class:`allmydata.node._Config` instance
|
|
"""
|
|
basedir = abspath_expanduser_unicode(unicode(basedir))
|
|
if _valid_config is None:
|
|
_valid_config = _common_valid_config()
|
|
|
|
# complain if there's bad stuff in the config dir
|
|
_error_about_old_config_files(basedir, generated_files)
|
|
|
|
# canonicalize the portnum file
|
|
portnumfile = os.path.join(basedir, portnumfile)
|
|
|
|
# (try to) read the main config file
|
|
config_fname = os.path.join(basedir, "tahoe.cfg")
|
|
parser = configparser.SafeConfigParser()
|
|
try:
|
|
parser = configutil.get_config(config_fname)
|
|
except EnvironmentError as e:
|
|
if e.errno != errno.ENOENT:
|
|
raise
|
|
|
|
configutil.validate_config(config_fname, parser, _valid_config)
|
|
|
|
# make sure we have a private configuration area
|
|
fileutil.make_dirs(os.path.join(basedir, "private"), 0o700)
|
|
|
|
return _Config(parser, portnumfile, basedir, config_fname)
|
|
|
|
|
|
def config_from_string(basedir, portnumfile, config_str, _valid_config=None):
|
|
"""
|
|
load and validate configuration from in-memory string
|
|
"""
|
|
if _valid_config is None:
|
|
_valid_config = _common_valid_config()
|
|
|
|
# load configuration from in-memory string
|
|
parser = configparser.SafeConfigParser()
|
|
parser.readfp(StringIO(config_str))
|
|
|
|
fname = "<in-memory>"
|
|
configutil.validate_config(fname, parser, _valid_config)
|
|
return _Config(parser, portnumfile, basedir, fname)
|
|
|
|
|
|
def get_app_versions():
|
|
"""
|
|
:returns: dict of versions important to Foolscap
|
|
"""
|
|
return dict(app_versions.versions)
|
|
|
|
|
|
def _error_about_old_config_files(basedir, generated_files):
|
|
"""
|
|
If any old configuration files are detected, raise
|
|
OldConfigError.
|
|
"""
|
|
oldfnames = set()
|
|
old_names = [
|
|
'nickname', 'webport', 'keepalive_timeout', 'log_gatherer.furl',
|
|
'disconnect_timeout', 'advertised_ip_addresses', 'introducer.furl',
|
|
'helper.furl', 'key_generator.furl', 'stats_gatherer.furl',
|
|
'no_storage', 'readonly_storage', 'sizelimit',
|
|
'debug_discard_storage', 'run_helper'
|
|
]
|
|
for fn in generated_files:
|
|
old_names.remove(fn)
|
|
for name in old_names:
|
|
fullfname = os.path.join(basedir, name)
|
|
if os.path.exists(fullfname):
|
|
oldfnames.add(fullfname)
|
|
if oldfnames:
|
|
e = OldConfigError(oldfnames)
|
|
twlog.msg(e)
|
|
raise e
|
|
|
|
|
|
class _Config(object):
|
|
"""
|
|
Manages configuration of a Tahoe 'node directory'.
|
|
|
|
Note: all this code and functionality was formerly in the Node
|
|
class; names and funtionality have been kept the same while moving
|
|
the code. It probably makes sense for several of these APIs to
|
|
have better names.
|
|
"""
|
|
|
|
def __init__(self, configparser, portnum_fname, basedir, config_fname):
|
|
"""
|
|
:param configparser: a ConfigParser instance
|
|
|
|
:param portnum_fname: filename to use for the port-number file
|
|
(a relative path inside basedir)
|
|
|
|
:param basedir: path to our "node directory", inside which all
|
|
configuration is managed
|
|
|
|
:param config_fname: the pathname actually used to create the
|
|
configparser (might be 'fake' if using in-memory data)
|
|
"""
|
|
self.portnum_fname = portnum_fname
|
|
self._basedir = abspath_expanduser_unicode(unicode(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
|
|
|
|
def validate(self, valid_config_sections):
|
|
configutil.validate_config(self._config_fname, self.config, valid_config_sections)
|
|
|
|
def write_config_file(self, name, value, mode="w"):
|
|
"""
|
|
writes the given 'value' into a file called 'name' in the config
|
|
directory
|
|
"""
|
|
fn = os.path.join(self._basedir, name)
|
|
try:
|
|
fileutil.write(fn, value, mode)
|
|
except EnvironmentError:
|
|
log.err(
|
|
Failure(),
|
|
"Unable to write config file '{}'".format(fn),
|
|
)
|
|
|
|
def items(self, section, default=_None):
|
|
try:
|
|
return self.config.items(section)
|
|
except configparser.NoSectionError:
|
|
if default is _None:
|
|
raise
|
|
return default
|
|
|
|
def get_config(self, section, option, default=_None, boolean=False):
|
|
try:
|
|
if boolean:
|
|
return self.config.getboolean(section, option)
|
|
|
|
item = self.config.get(section, option)
|
|
if option.endswith(".furl") and self._contains_unescaped_hash(item):
|
|
raise UnescapedHashError(section, option, item)
|
|
|
|
return item
|
|
except (configparser.NoOptionError, configparser.NoSectionError):
|
|
if default is _None:
|
|
raise MissingConfigEntry(
|
|
"{} is missing the [{}]{} entry".format(
|
|
quote_output(self._config_fname),
|
|
section,
|
|
option,
|
|
)
|
|
)
|
|
return default
|
|
|
|
def get_config_from_file(self, name, required=False):
|
|
"""Get the (string) contents of a config file, or None if the file
|
|
did not exist. If required=True, raise an exception rather than
|
|
returning None. Any leading or trailing whitespace will be stripped
|
|
from the data."""
|
|
fn = os.path.join(self._basedir, name)
|
|
try:
|
|
return fileutil.read(fn).strip()
|
|
except EnvironmentError as e:
|
|
if e.errno != errno.ENOENT:
|
|
raise # we only care about "file doesn't exist"
|
|
if not required:
|
|
return None
|
|
raise
|
|
|
|
def get_or_create_private_config(self, name, default=_None):
|
|
"""Try to get the (string) contents of a private config file (which
|
|
is a config file that resides within the subdirectory named
|
|
'private'), and return it. Any leading or trailing whitespace will be
|
|
stripped from the data.
|
|
|
|
If the file does not exist, and default is not given, report an error.
|
|
If the file does not exist and a default is specified, try to create
|
|
it using that default, and then return the value that was written.
|
|
If 'default' is a string, use it as a default value. If not, treat it
|
|
as a zero-argument callable that is expected to return a string.
|
|
"""
|
|
privname = os.path.join(self._basedir, "private", name)
|
|
try:
|
|
value = fileutil.read(privname)
|
|
except EnvironmentError as e:
|
|
if e.errno != errno.ENOENT:
|
|
raise # we only care about "file doesn't exist"
|
|
if default is _None:
|
|
raise MissingConfigEntry("The required configuration file %s is missing."
|
|
% (quote_output(privname),))
|
|
if isinstance(default, (bytes, unicode)):
|
|
value = default
|
|
else:
|
|
value = default()
|
|
fileutil.write(privname, value)
|
|
return value.strip()
|
|
|
|
def write_private_config(self, name, value):
|
|
"""Write the (string) contents of a private config file (which is a
|
|
config file that resides within the subdirectory named 'private'), and
|
|
return it.
|
|
"""
|
|
privname = os.path.join(self._basedir, "private", name)
|
|
with open(privname, "wb") as f:
|
|
f.write(value)
|
|
|
|
def get_private_config(self, name, default=_None):
|
|
"""Read the (string) contents of a private config file (which is a
|
|
config file that resides within the subdirectory named 'private'),
|
|
and return it. Return a default, or raise an error if one was not
|
|
given.
|
|
"""
|
|
privname = os.path.join(self._basedir, "private", name)
|
|
try:
|
|
return fileutil.read(privname).strip()
|
|
except EnvironmentError as e:
|
|
if e.errno != errno.ENOENT:
|
|
raise # we only care about "file doesn't exist"
|
|
if default is _None:
|
|
raise MissingConfigEntry("The required configuration file %s is missing."
|
|
% (quote_output(privname),))
|
|
return default
|
|
|
|
def get_private_path(self, *args):
|
|
"""
|
|
returns an absolute path inside the 'private' directory with any
|
|
extra args join()-ed
|
|
"""
|
|
return os.path.join(self._basedir, "private", *args)
|
|
|
|
def get_config_path(self, *args):
|
|
"""
|
|
returns an absolute path inside the config directory with any
|
|
extra args join()-ed
|
|
"""
|
|
# note: we re-expand here (_basedir already went through this
|
|
# expanduser function) in case the path we're being asked for
|
|
# has embedded ".."'s in it
|
|
return abspath_expanduser_unicode(
|
|
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):
|
|
"""
|
|
:param config: a _Config instance
|
|
|
|
:returns: dict containing all Foolscap Tub-related options,
|
|
overriding defaults with appropriate config from `config`
|
|
instance.
|
|
"""
|
|
# We can't unify the camelCase vs. dashed-name divide here,
|
|
# because these are options for Foolscap
|
|
tub_options = {
|
|
"logLocalFailures": True,
|
|
"logRemoteFailures": True,
|
|
"expose-remote-exception-types": False,
|
|
"accept-gifts": False,
|
|
}
|
|
|
|
# see #521 for a discussion of how to pick these timeout values.
|
|
keepalive_timeout_s = config.get_config("node", "timeout.keepalive", "")
|
|
if keepalive_timeout_s:
|
|
tub_options["keepaliveTimeout"] = int(keepalive_timeout_s)
|
|
disconnect_timeout_s = config.get_config("node", "timeout.disconnect", "")
|
|
if disconnect_timeout_s:
|
|
# N.B.: this is in seconds, so use "1800" to get 30min
|
|
tub_options["disconnectTimeout"] = int(disconnect_timeout_s)
|
|
return tub_options
|
|
|
|
|
|
def _make_tcp_handler():
|
|
"""
|
|
:returns: a Foolscap default TCP handler
|
|
"""
|
|
# this is always available
|
|
from foolscap.connections.tcp import default
|
|
return default()
|
|
|
|
|
|
def create_connection_handlers(reactor, config, i2p_provider, tor_provider):
|
|
"""
|
|
:returns: 2-tuple of default_connection_handlers, foolscap_connection_handlers
|
|
"""
|
|
reveal_ip = config.get_config("node", "reveal-IP-address", True, boolean=True)
|
|
|
|
# We store handlers for everything. None means we were unable to
|
|
# create that handler, so hints which want it will be ignored.
|
|
handlers = foolscap_connection_handlers = {
|
|
"tcp": _make_tcp_handler(),
|
|
"tor": tor_provider.get_tor_handler(),
|
|
"i2p": i2p_provider.get_i2p_handler(),
|
|
}
|
|
log.msg(
|
|
format="built Foolscap connection handlers for: %(known_handlers)s",
|
|
known_handlers=sorted([k for k,v in handlers.items() if v]),
|
|
facility="tahoe.node",
|
|
umid="PuLh8g",
|
|
)
|
|
|
|
# then we remember the default mappings from tahoe.cfg
|
|
default_connection_handlers = {"tor": "tor", "i2p": "i2p"}
|
|
tcp_handler_name = config.get_config("connections", "tcp", "tcp").lower()
|
|
if tcp_handler_name == "disabled":
|
|
default_connection_handlers["tcp"] = None
|
|
else:
|
|
if tcp_handler_name not in handlers:
|
|
raise ValueError(
|
|
"'tahoe.cfg [connections] tcp=' uses "
|
|
"unknown handler type '{}'".format(
|
|
tcp_handler_name
|
|
)
|
|
)
|
|
if not handlers[tcp_handler_name]:
|
|
raise ValueError(
|
|
"'tahoe.cfg [connections] tcp=' uses "
|
|
"unavailable/unimportable handler type '{}'. "
|
|
"Please pip install tahoe-lafs[{}] to fix.".format(
|
|
tcp_handler_name,
|
|
tcp_handler_name,
|
|
)
|
|
)
|
|
default_connection_handlers["tcp"] = tcp_handler_name
|
|
|
|
if not reveal_ip:
|
|
if default_connection_handlers.get("tcp") == "tcp":
|
|
raise PrivacyError("tcp = tcp, must be set to 'tor' or 'disabled'")
|
|
return default_connection_handlers, foolscap_connection_handlers
|
|
|
|
|
|
|
|
def create_tub(tub_options, default_connection_handlers, foolscap_connection_handlers,
|
|
handler_overrides={}, **kwargs):
|
|
"""
|
|
Create a Tub with the right options and handlers. It will be
|
|
ephemeral unless the caller provides certFile= in kwargs
|
|
|
|
:param handler_overrides: anything in this will override anything
|
|
in `default_connection_handlers` for just this call.
|
|
|
|
:param dict tub_options: every key-value pair in here will be set in
|
|
the new Tub via `Tub.setOption`
|
|
"""
|
|
tub = Tub(**kwargs)
|
|
for (name, value) in 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():
|
|
handler = foolscap_connection_handlers.get(handler_name)
|
|
if handler:
|
|
tub.addConnectionHintHandler(hint_type, handler)
|
|
return tub
|
|
|
|
|
|
def _convert_tub_port(s):
|
|
"""
|
|
:returns: a proper Twisted endpoint string like (`tcp:X`) is `s`
|
|
is a bare number, or returns `s` as-is
|
|
"""
|
|
if re.search(r'^\d+$', s):
|
|
return "tcp:{}".format(int(s))
|
|
return s
|
|
|
|
|
|
def _tub_portlocation(config):
|
|
"""
|
|
:returns: None or tuple of (port, location) for the main tub based
|
|
on the given configuration. May raise ValueError or PrivacyError
|
|
if there are problems with the config
|
|
"""
|
|
cfg_tubport = config.get_config("node", "tub.port", None)
|
|
cfg_location = config.get_config("node", "tub.location", None)
|
|
reveal_ip = config.get_config("node", "reveal-IP-address", True, boolean=True)
|
|
tubport_disabled = False
|
|
|
|
if cfg_tubport is not None:
|
|
cfg_tubport = cfg_tubport.strip()
|
|
if cfg_tubport == "":
|
|
raise ValueError("tub.port must not be empty")
|
|
if cfg_tubport == "disabled":
|
|
tubport_disabled = True
|
|
|
|
location_disabled = False
|
|
if cfg_location is not None:
|
|
cfg_location = cfg_location.strip()
|
|
if cfg_location == "":
|
|
raise ValueError("tub.location must not be empty")
|
|
if cfg_location == "disabled":
|
|
location_disabled = True
|
|
|
|
if tubport_disabled and location_disabled:
|
|
return None
|
|
if tubport_disabled and not location_disabled:
|
|
raise ValueError("tub.port is disabled, but not tub.location")
|
|
if location_disabled and not tubport_disabled:
|
|
raise ValueError("tub.location is disabled, but not tub.port")
|
|
|
|
if cfg_tubport is None:
|
|
# For 'tub.port', tahoe.cfg overrides the individual file on
|
|
# disk. So only read config.portnum_fname if tahoe.cfg doesn't
|
|
# provide a value.
|
|
if os.path.exists(config.portnum_fname):
|
|
file_tubport = fileutil.read(config.portnum_fname).strip()
|
|
tubport = _convert_tub_port(file_tubport)
|
|
else:
|
|
tubport = "tcp:%d" % iputil.allocate_tcp_port()
|
|
fileutil.write_atomically(config.portnum_fname, tubport + "\n",
|
|
mode="")
|
|
else:
|
|
tubport = _convert_tub_port(cfg_tubport)
|
|
|
|
for port in tubport.split(","):
|
|
if port in ("0", "tcp:0"):
|
|
raise ValueError("tub.port cannot be 0: you must choose")
|
|
|
|
if cfg_location is None:
|
|
cfg_location = "AUTO"
|
|
|
|
local_portnum = None # needed to hush lgtm.com static analyzer
|
|
# Replace the location "AUTO", if present, with the detected local
|
|
# addresses. Don't probe for local addresses unless necessary.
|
|
split_location = cfg_location.split(",")
|
|
if "AUTO" in split_location:
|
|
if not reveal_ip:
|
|
raise PrivacyError("tub.location uses AUTO")
|
|
local_addresses = iputil.get_local_addresses_sync()
|
|
# tubport must be like "tcp:12345" or "tcp:12345:morestuff"
|
|
local_portnum = int(tubport.split(":")[1])
|
|
new_locations = []
|
|
for loc in split_location:
|
|
if loc == "AUTO":
|
|
new_locations.extend(["tcp:%s:%d" % (ip, local_portnum)
|
|
for ip in local_addresses])
|
|
else:
|
|
if not reveal_ip:
|
|
# Legacy hints are "host:port". We use Foolscap's utility
|
|
# function to convert all hints into the modern format
|
|
# ("tcp:host:port") because that's what the receiving
|
|
# client will probably do. We test the converted hint for
|
|
# TCP-ness, but publish the original hint because that
|
|
# was the user's intent.
|
|
from foolscap.connections.tcp import convert_legacy_hint
|
|
converted_hint = convert_legacy_hint(loc)
|
|
hint_type = converted_hint.split(":")[0]
|
|
if hint_type == "tcp":
|
|
raise PrivacyError("tub.location includes tcp: hint")
|
|
new_locations.append(loc)
|
|
location = ",".join(new_locations)
|
|
|
|
return tubport, location
|
|
|
|
|
|
def create_main_tub(config, tub_options,
|
|
default_connection_handlers, foolscap_connection_handlers,
|
|
i2p_provider, tor_provider,
|
|
handler_overrides={}, cert_filename="node.pem"):
|
|
"""
|
|
Creates a 'main' Foolscap Tub, typically for use as the top-level
|
|
access point for a running Node.
|
|
|
|
:param Config: a `_Config` instance
|
|
|
|
:param dict tub_options: any options to change in the tub
|
|
|
|
:param default_connection_handlers: default Foolscap connection
|
|
handlers
|
|
|
|
:param foolscap_connection_handlers: Foolscap connection
|
|
handlers for this tub
|
|
|
|
:param i2p_provider: None, or a _Provider instance if I2P is
|
|
installed.
|
|
|
|
:param tor_provider: None, or a _Provider instance if txtorcon +
|
|
Tor are installed.
|
|
"""
|
|
portlocation = _tub_portlocation(config)
|
|
|
|
certfile = config.get_private_path("node.pem") # FIXME? "node.pem" was the CERTFILE option/thing
|
|
tub = create_tub(tub_options, default_connection_handlers, foolscap_connection_handlers,
|
|
handler_overrides=handler_overrides, certFile=certfile)
|
|
|
|
if portlocation:
|
|
tubport, location = portlocation
|
|
for port in tubport.split(","):
|
|
if port == "listen:i2p":
|
|
# the I2P provider will read its section of tahoe.cfg and
|
|
# return either a fully-formed Endpoint, or a descriptor
|
|
# that will create one, so we don't have to stuff all the
|
|
# options into the tub.port string (which would need a lot
|
|
# of escaping)
|
|
port_or_endpoint = i2p_provider.get_listener()
|
|
elif port == "listen:tor":
|
|
port_or_endpoint = tor_provider.get_listener()
|
|
else:
|
|
port_or_endpoint = port
|
|
tub.listenOn(port_or_endpoint)
|
|
tub.setLocation(location)
|
|
log.msg("Tub location set to %s" % (location,))
|
|
# the Tub is now ready for tub.registerReference()
|
|
else:
|
|
log.msg("Tub is not listening")
|
|
|
|
return tub
|
|
|
|
|
|
def create_control_tub():
|
|
"""
|
|
Creates a Foolscap Tub for use by the control port. This is a
|
|
localhost-only ephemeral Tub, with no control over the listening
|
|
port or location
|
|
"""
|
|
control_tub = Tub()
|
|
portnum = iputil.listenOnUnused(control_tub)
|
|
log.msg("Control Tub location set to 127.0.0.1:%s" % (portnum,))
|
|
return control_tub
|
|
|
|
|
|
class Node(service.MultiService):
|
|
"""
|
|
This class implements common functionality of both Client nodes and Introducer nodes.
|
|
"""
|
|
NODETYPE = "unknown NODETYPE"
|
|
CERTFILE = "node.pem"
|
|
GENERATED_FILES = []
|
|
|
|
def __init__(self, config, main_tub, control_tub, i2p_provider, tor_provider):
|
|
"""
|
|
Initialize the node with the given configuration. Its base directory
|
|
is the current directory by default.
|
|
"""
|
|
service.MultiService.__init__(self)
|
|
|
|
self.config = config
|
|
self.get_config = config.get_config # XXX stopgap
|
|
self.nickname = config.nickname # XXX stopgap
|
|
|
|
# this can go away once Client.init_client_storage_broker is moved into create_client()
|
|
# (tests sometimes have None here)
|
|
self._i2p_provider = i2p_provider
|
|
self._tor_provider = tor_provider
|
|
|
|
self.init_tempdir()
|
|
|
|
self.create_log_tub()
|
|
self.logSource = "Node"
|
|
self.setup_logging()
|
|
|
|
self.tub = main_tub
|
|
if self.tub is not None:
|
|
self.nodeid = b32decode(self.tub.tubID.upper()) # binary format
|
|
self.short_nodeid = b32encode(self.nodeid).lower()[:8] # for printing
|
|
self.config.write_config_file("my_nodeid", b32encode(self.nodeid).lower() + "\n")
|
|
self.tub.setServiceParent(self)
|
|
else:
|
|
self.nodeid = self.short_nodeid = None
|
|
|
|
self.control_tub = control_tub
|
|
if self.control_tub is not None:
|
|
self.control_tub.setServiceParent(self)
|
|
|
|
self.log("Node constructed. " + get_package_versions_string())
|
|
iputil.increase_rlimits()
|
|
|
|
def _is_tub_listening(self):
|
|
"""
|
|
:returns: True if the main tub is listening
|
|
"""
|
|
return len(self.tub.getListeners()) > 0
|
|
|
|
def init_tempdir(self):
|
|
"""
|
|
Initialize/create a directory for temporary files.
|
|
"""
|
|
tempdir_config = self.config.get_config("node", "tempdir", "tmp")
|
|
if isinstance(tempdir_config, bytes):
|
|
tempdir_config = tempdir_config.decode('utf-8')
|
|
tempdir = self.config.get_config_path(tempdir_config)
|
|
if not os.path.exists(tempdir):
|
|
fileutil.make_dirs(tempdir)
|
|
tempfile.tempdir = tempdir
|
|
# this should cause twisted.web.http (which uses
|
|
# tempfile.TemporaryFile) to put large request bodies in the given
|
|
# directory. Without this, the default temp dir is usually /tmp/,
|
|
# which is frequently too small.
|
|
temp_fd, test_name = tempfile.mkstemp()
|
|
_assert(os.path.dirname(test_name) == tempdir, test_name, tempdir)
|
|
os.close(temp_fd) # avoid leak of unneeded fd
|
|
|
|
# pull this outside of Node's __init__ too, see:
|
|
# https://tahoe-lafs.org/trac/tahoe-lafs/ticket/2948
|
|
def create_log_tub(self):
|
|
# The logport uses a localhost-only ephemeral Tub, with no control
|
|
# over the listening port or location. This might change if we
|
|
# discover a compelling reason for it in the future (e.g. being able
|
|
# to use "flogtool tail" against a remote server), but for now I
|
|
# think we can live without it.
|
|
self.log_tub = Tub()
|
|
portnum = iputil.listenOnUnused(self.log_tub)
|
|
self.log("Log Tub location set to 127.0.0.1:%s" % (portnum,))
|
|
self.log_tub.setServiceParent(self)
|
|
|
|
def startService(self):
|
|
# Note: this class can be started and stopped at most once.
|
|
self.log("Node.startService")
|
|
# Record the process id in the twisted log, after startService()
|
|
# (__init__ is called before fork(), but startService is called
|
|
# after). Note that Foolscap logs handle pid-logging by itself, no
|
|
# need to send a pid to the foolscap log here.
|
|
twlog.msg("My pid: %s" % os.getpid())
|
|
try:
|
|
os.chmod("twistd.pid", 0o644)
|
|
except EnvironmentError:
|
|
pass
|
|
|
|
service.MultiService.startService(self)
|
|
self.log("%s running" % self.NODETYPE)
|
|
twlog.msg("%s running" % self.NODETYPE)
|
|
|
|
def stopService(self):
|
|
self.log("Node.stopService")
|
|
return service.MultiService.stopService(self)
|
|
|
|
def shutdown(self):
|
|
"""Shut down the node. Returns a Deferred that fires (with None) when
|
|
it finally stops kicking."""
|
|
self.log("Node.shutdown")
|
|
return self.stopService()
|
|
|
|
def setup_logging(self):
|
|
# we replace the formatTime() method of the log observer that
|
|
# twistd set up for us, with a method that uses our preferred
|
|
# timestamp format.
|
|
for o in twlog.theLogPublisher.observers:
|
|
# o might be a FileLogObserver's .emit method
|
|
if type(o) is type(self.setup_logging): # bound method
|
|
ob = o.__self__
|
|
if isinstance(ob, twlog.FileLogObserver):
|
|
newmeth = types.MethodType(formatTimeTahoeStyle, ob)
|
|
ob.formatTime = newmeth
|
|
# TODO: twisted >2.5.0 offers maxRotatedFiles=50
|
|
|
|
lgfurl_file = self.config.get_private_path("logport.furl").encode(get_filesystem_encoding())
|
|
if os.path.exists(lgfurl_file):
|
|
os.remove(lgfurl_file)
|
|
self.log_tub.setOption("logport-furlfile", lgfurl_file)
|
|
lgfurl = self.config.get_config("node", "log_gatherer.furl", "")
|
|
if lgfurl:
|
|
# this is in addition to the contents of log-gatherer-furlfile
|
|
self.log_tub.setOption("log-gatherer-furl", lgfurl)
|
|
self.log_tub.setOption("log-gatherer-furlfile",
|
|
self.config.get_config_path("log_gatherer.furl"))
|
|
|
|
incident_dir = self.config.get_config_path("logs", "incidents")
|
|
foolscap.logging.log.setLogDir(incident_dir.encode(get_filesystem_encoding()))
|
|
twlog.msg("Foolscap logging initialized")
|
|
twlog.msg("Note to developers: twistd.log does not receive very much.")
|
|
twlog.msg("Use 'flogtool tail -c NODEDIR/private/logport.furl' instead")
|
|
twlog.msg("and read docs/logging.rst")
|
|
|
|
def log(self, *args, **kwargs):
|
|
return log.msg(*args, **kwargs)
|