Changeset c385e95 in trunk
- Timestamp:
- 2020-04-23T18:24:00Z (5 years ago)
- Branches:
- master
- Children:
- 4dc3702
- Parents:
- 8df1ed1b (diff), bd8bf0f (diff)
Note: this is a merge changeset, the changes displayed below correspond to the merge itself.
Use the (diff) links above to see all the changes relative to each parent. - Files:
-
- 5 added
- 20 deleted
- 28 edited
- 1 moved
Legend:
- Unmodified
- Added
- Removed
-
TabularUnified .circleci/Dockerfile.pypy ¶
r8df1ed1b rc385e95 1 FROM pypy:2.7- 7.1.1-jessie1 FROM pypy:2.7-buster 2 2 3 3 ENV WHEELHOUSE_PATH /tmp/wheelhouse -
TabularUnified .circleci/config.yml ¶
r8df1ed1b rc385e95 28 28 - "nixos-19.09" 29 29 30 # Test against PyPy 2.7 /7.1.131 - "pypy2.7- 7.1"30 # Test against PyPy 2.7 31 - "pypy2.7-buster" 32 32 33 33 # Other assorted tasks and configurations … … 70 70 - "build-image-centos-8" 71 71 - "build-image-slackware-14.2" 72 - "build-image-pypy-2.7- 7.1.1-jessie"72 - "build-image-pypy-2.7-buster" 73 73 74 74 … … 199 199 200 200 201 pypy2.7- 7.1:202 <<: *DEBIAN 203 docker: 204 - image: "tahoelafsci/pypy:2.7- 7.1.1-jessie"201 pypy2.7-buster: 202 <<: *DEBIAN 203 docker: 204 - image: "tahoelafsci/pypy:2.7-buster" 205 205 user: "nobody" 206 206 … … 514 514 515 515 516 build-image-pypy-2.7- 7.1.1-jessie:516 build-image-pypy-2.7-buster: 517 517 <<: *BUILD_IMAGE 518 518 519 519 environment: 520 520 DISTRO: "pypy" 521 TAG: "2.7- 7.1.1-jessie"521 TAG: "2.7-buster" -
TabularUnified .github/workflows/ci.yml ¶
r8df1ed1b rc385e95 3 3 on: 4 4 push: 5 branches: 6 - "master" 5 7 pull_request: 6 8 -
TabularUnified .gitignore ¶
r8df1ed1b rc385e95 44 44 /docs/_build/ 45 45 /coverage.xml 46 /smoke_magicfolder/47 46 /.hypothesis/ 48 47 -
TabularUnified Makefile ¶
r8df1ed1b rc385e95 42 42 # echo not uploading tahoe-lafs-osx-pkg because this is not trunk but is branch \"${BB_BRANCH}\" ; \ 43 43 # fi 44 45 .PHONY: smoketest46 smoketest:47 -python ./src/allmydata/test/check_magicfolder_smoke.py kill48 -rm -rf smoke_magicfolder/49 python ./src/allmydata/test/check_magicfolder_smoke.py50 44 51 45 # code coverage-based testing is disabled temporarily, as we switch to tox. -
TabularUnified docs/configuration.rst ¶
r8df1ed1b rc385e95 83 83 * SFTP service 84 84 * FTP service 85 * Magic Folder service86 85 * helper service 87 86 * storage service. … … 719 718 for instructions on configuring these services, and the ``[sftpd]`` and 720 719 ``[ftpd]`` sections of ``tahoe.cfg``. 721 722 Magic Folder723 724 A node running on Linux or Windows can be configured to automatically725 upload files that are created or changed in a specified local directory.726 See :doc:`frontends/magic-folder` for details.727 720 728 721 -
TabularUnified docs/index.rst ¶
r8df1ed1b rc385e95 21 21 frontends/webapi 22 22 frontends/FTP-and-SFTP 23 frontends/magic-folder24 23 frontends/download-status 25 24 … … 38 37 cautions 39 38 write_coordination 40 magic-folder-howto41 39 backupdb 42 40 -
TabularUnified docs/magic-wormhole-invites.rst ¶
r8df1ed1b rc385e95 20 20 Inside Tahoe-LAFS we are using a channel created using `magic 21 21 wormhole`_ to exchange configuration and the secret fURL of the 22 Introducer with new clients. In the future, we would like to make the 23 Magic Folder (:ref:`Magic Folder HOWTO <magic-folder-howto>`) invites and joins work this way 24 as well. 22 Introducer with new clients. 25 23 26 24 This is a two-part process. Alice runs a grid and wishes to have her -
TabularUnified docs/proposed/index.rst ¶
r8df1ed1b rc385e95 15 15 16 16 leasedb 17 magic-folder/filesystem-integration18 magic-folder/remote-to-local-sync19 magic-folder/user-interface-design20 magic-folder/multi-party-conflict-detection21 17 http-storage-node-protocol -
TabularUnified integration/conftest.py ¶
r8df1ed1b rc385e95 333 333 @log_call(action_type=u"integration:alice", include_args=[], include_result=False) 334 334 def alice(reactor, temp_dir, introducer_furl, flog_gatherer, storage_nodes, request): 335 try:336 mkdir(join(temp_dir, 'magic-alice'))337 except OSError:338 pass339 340 335 process = pytest_twisted.blockon( 341 336 _create_node( … … 352 347 @log_call(action_type=u"integration:bob", include_args=[], include_result=False) 353 348 def bob(reactor, temp_dir, introducer_furl, flog_gatherer, storage_nodes, request): 354 try:355 mkdir(join(temp_dir, 'magic-bob'))356 except OSError:357 pass358 359 349 process = pytest_twisted.blockon( 360 350 _create_node( … … 366 356 await_client_ready(process) 367 357 return process 368 369 370 @pytest.fixture(scope='session')371 @log_call(action_type=u"integration:alice:invite", include_args=["temp_dir"])372 def alice_invite(reactor, alice, temp_dir, request):373 node_dir = join(temp_dir, 'alice')374 375 with start_action(action_type=u"integration:alice:magic_folder:create"):376 # FIXME XXX by the time we see "client running" in the logs, the377 # storage servers aren't "really" ready to roll yet (uploads fairly378 # consistently fail if we don't hack in this pause...)379 proto = _CollectOutputProtocol()380 _tahoe_runner_optional_coverage(381 proto,382 reactor,383 request,384 [385 'magic-folder', 'create',386 '--poll-interval', '2',387 '--basedir', node_dir, 'magik:', 'alice',388 join(temp_dir, 'magic-alice'),389 ]390 )391 pytest_twisted.blockon(proto.done)392 393 with start_action(action_type=u"integration:alice:magic_folder:invite") as a:394 proto = _CollectOutputProtocol()395 _tahoe_runner_optional_coverage(396 proto,397 reactor,398 request,399 [400 'magic-folder', 'invite',401 '--basedir', node_dir, 'magik:', 'bob',402 ]403 )404 pytest_twisted.blockon(proto.done)405 invite = proto.output.getvalue()406 a.add_success_fields(invite=invite)407 408 with start_action(action_type=u"integration:alice:magic_folder:restart"):409 # before magic-folder works, we have to stop and restart (this is410 # crappy for the tests -- can we fix it in magic-folder?)411 try:412 alice.transport.signalProcess('TERM')413 pytest_twisted.blockon(alice.transport.exited)414 except ProcessExitedAlready:415 pass416 with start_action(action_type=u"integration:alice:magic_folder:magic-text"):417 magic_text = 'Completed initial Magic Folder scan successfully'418 pytest_twisted.blockon(_run_node(reactor, node_dir, request, magic_text))419 await_client_ready(alice)420 return invite421 422 423 @pytest.fixture(scope='session')424 @log_call(425 action_type=u"integration:magic_folder",426 include_args=["alice_invite", "temp_dir"],427 )428 def magic_folder(reactor, alice_invite, alice, bob, temp_dir, request):429 print("pairing magic-folder")430 bob_dir = join(temp_dir, 'bob')431 proto = _CollectOutputProtocol()432 _tahoe_runner_optional_coverage(433 proto,434 reactor,435 request,436 [437 'magic-folder', 'join',438 '--poll-interval', '1',439 '--basedir', bob_dir,440 alice_invite,441 join(temp_dir, 'magic-bob'),442 ]443 )444 pytest_twisted.blockon(proto.done)445 446 # before magic-folder works, we have to stop and restart (this is447 # crappy for the tests -- can we fix it in magic-folder?)448 try:449 print("Sending TERM to Bob")450 bob.transport.signalProcess('TERM')451 pytest_twisted.blockon(bob.transport.exited)452 except ProcessExitedAlready:453 pass454 455 magic_text = 'Completed initial Magic Folder scan successfully'456 pytest_twisted.blockon(_run_node(reactor, bob_dir, request, magic_text))457 await_client_ready(bob)458 return (join(temp_dir, 'magic-alice'), join(temp_dir, 'magic-bob'))459 358 460 359 -
TabularUnified integration/test_aaa_aardvark.py ¶
r8df1ed1b rc385e95 17 17 def test_create_storage(storage_nodes): 18 18 print("Created {} storage nodes".format(len(storage_nodes))) 19 20 21 def test_create_alice_bob_magicfolder(magic_folder):22 print("Alice and Bob have paired magic-folders") -
TabularUnified integration/test_tor.py ¶
r8df1ed1b rc385e95 15 15 import util 16 16 17 # see "conftest.py" for the fixtures (e.g. " magic_folder")17 # see "conftest.py" for the fixtures (e.g. "tor_network") 18 18 19 19 @pytest_twisted.inlineCallbacks -
TabularUnified integration/util.py ¶
r8df1ed1b rc385e95 499 499 ) 500 500 ) 501 502 503 def magic_folder_cli(request, reactor, node_dir, *argv):504 return cli(request, reactor, node_dir, "magic-folder", *argv) -
TabularUnified nix/eliot.nix ¶
r8df1ed1b rc385e95 13 13 substituteInPlace setup.py \ 14 14 --replace "boltons >= 19.0.1" boltons 15 # depends on eliot.prettyprint._main which we don't have here. 16 rm eliot/tests/test_prettyprint.py 15 ''; 17 16 18 # Fails intermittently.19 substituteInPlace eliot/tests/test_validation.py \20 --replace "def test_omitLoggerFromActionType" "def xtest_omitLoggerFromActionType" \21 --replace "def test_logCallsDefaultLoggerWrite" "def xtest_logCallsDefaultLoggerWrite"22 '';17 # A seemingly random subset of the test suite fails intermittently. After 18 # Tahoe-LAFS is ported to Python 3 we can update to a newer Eliot and, if 19 # the test suite continues to fail, maybe it will be more likely that we can 20 # have upstream fix it for us. 21 doCheck = false; 23 22 24 23 checkInputs = [ testtools pytest hypothesis ]; -
TabularUnified setup.py ¶
r8df1ed1b rc385e95 63 63 "cryptography >= 2.6", 64 64 65 # * On Linux we need at least Twisted 10.1.0 for inotify support 66 # used by the drop-upload frontend. 67 # * We also need Twisted 10.1.0 for the FTP frontend in order for 65 # * We need Twisted 10.1.0 for the FTP frontend in order for 68 66 # Twisted's FTP server to support asynchronous close. 69 67 # * The SFTP frontend depends on Twisted 11.0.0 to fix the SSH server … … 355 353 # discussion. 356 354 ':sys_platform=="win32"': ["pywin32 != 226"], 357 ':sys_platform!="win32" and sys_platform!="linux2"': ["watchdog"], # For magic-folder on "darwin" (macOS) and the BSDs358 355 "test": [ 359 356 # Pin a specific pyflakes so we don't have different folks -
TabularUnified src/allmydata/client.py ¶
r8df1ed1b rc385e95 85 85 "stats_gatherer.furl", 86 86 "storage.plugins", 87 ),88 "drop_upload": ( # deprecated already?89 "enabled",90 87 ), 91 88 "ftpd": ( … … 122 119 "enabled", 123 120 ), 124 "magic_folder": (125 "download.umask",126 "enabled",127 "local.directory",128 "poll_interval",129 ),130 121 }, 131 122 is_valid_section=_is_valid_section, … … 682 673 node.Node.__init__(self, config, main_tub, control_tub, i2p_provider, tor_provider) 683 674 684 self._magic_folders = dict()685 675 self.started_timestamp = time.time() 686 676 self.logSource = "Client" … … 708 698 self.init_ftp_server() 709 699 self.init_sftp_server() 710 self.init_magic_folder()711 700 712 701 # If the node sees an exit_trigger file, it will poll every second to see … … 969 958 random data in "api_auth_token" which must be echoed to API 970 959 calls. 971 972 Currently only the URI '/magic' for magic-folder status; other973 endpoints are invited to include this as well, as appropriate.974 960 """ 975 961 return self.config.get_private_config('api_auth_token') … … 1089 1075 s.setServiceParent(self) 1090 1076 1091 def init_magic_folder(self):1092 #print "init_magic_folder"1093 if self.config.get_config("drop_upload", "enabled", False, boolean=True):1094 raise node.OldConfigOptionError(1095 "The [drop_upload] section must be renamed to [magic_folder].\n"1096 "See docs/frontends/magic-folder.rst for more information."1097 )1098 1099 if self.config.get_config("magic_folder", "enabled", False, boolean=True):1100 from allmydata.frontends import magic_folder1101 1102 try:1103 magic_folders = magic_folder.load_magic_folders(self.config._basedir)1104 except Exception as e:1105 log.msg("Error loading magic-folder config: {}".format(e))1106 raise1107 1108 # start processing the upload queue when we've connected to1109 # enough servers1110 threshold = min(self.encoding_params["k"],1111 self.encoding_params["happy"] + 1)1112 1113 for (name, mf_config) in magic_folders.items():1114 self.log("Starting magic_folder '{}'".format(name))1115 s = magic_folder.MagicFolder.from_config(self, name, mf_config)1116 self._magic_folders[name] = s1117 s.setServiceParent(self)1118 1119 connected_d = self.storage_broker.when_connected_enough(threshold)1120 def connected_enough(ign, mf):1121 mf.ready() # returns a Deferred we ignore1122 return None1123 connected_d.addCallback(connected_enough, s)1124 1125 1077 def _check_exit_trigger(self, exit_trigger_file): 1126 1078 if os.path.exists(exit_trigger_file): -
TabularUnified src/allmydata/scripts/runner.py ¶
r8df1ed1b rc385e95 10 10 from allmydata.scripts.common import get_default_nodedir 11 11 from allmydata.scripts import debug, create_node, cli, \ 12 stats_gatherer, admin, magic_folder_cli,tahoe_daemonize, tahoe_start, \12 stats_gatherer, admin, tahoe_daemonize, tahoe_start, \ 13 13 tahoe_stop, tahoe_restart, tahoe_run, tahoe_invite 14 14 from allmydata.util.encodingutil import quote_output, quote_local_unicode_path, get_io_encoding … … 62 62 + debug.subCommands 63 63 + cli.subCommands 64 + magic_folder_cli.subCommands65 64 + tahoe_invite.subCommands 66 65 ) … … 155 154 f0 = cli.dispatch[command] 156 155 f = lambda so: threads.deferToThread(f0, so) 157 elif command in magic_folder_cli.dispatch:158 # same159 f0 = magic_folder_cli.dispatch[command]160 f = lambda so: threads.deferToThread(f0, so)161 156 elif command in tahoe_invite.dispatch: 162 157 f = tahoe_invite.dispatch[command] -
TabularUnified src/allmydata/test/__init__.py ¶
r8df1ed1b rc385e95 1 # -*- coding: utf-8 -*- 2 # Tahoe-LAFS -- secure, distributed storage grid 3 # 4 # Copyright © 2020 The Tahoe-LAFS Software Foundation 5 # 6 # This file is part of Tahoe-LAFS. 7 # 8 # See the docs/about.rst file for licensing information. 9 10 """ 11 Some setup that should apply across the entire test suite. 12 13 Rather than defining interesting APIs for other code to use, this just causes 14 some side-effects which make things better when the test suite runs. 15 """ 16 17 from traceback import extract_stack, format_list 18 from foolscap.pb import Listener 19 from twisted.python.log import err 20 from twisted.application import service 21 1 22 2 23 from foolscap.logging.incident import IncidentQualifier … … 53 74 _configure_hypothesis() 54 75 76 def logging_for_pb_listener(): 77 """ 78 Make Foolscap listen error reports include Listener creation stack 79 information. 80 """ 81 original__init__ = Listener.__init__ 82 def _listener__init__(self, *a, **kw): 83 original__init__(self, *a, **kw) 84 # Capture the stack here, where Listener is instantiated. This is 85 # likely to explain what code is responsible for this Listener, useful 86 # information to have when the Listener eventually fails to listen. 87 self._creation_stack = extract_stack() 88 89 # Override the Foolscap implementation with one that has an errback 90 def _listener_startService(self): 91 service.Service.startService(self) 92 d = self._ep.listen(self) 93 def _listening(lp): 94 self._lp = lp 95 d.addCallbacks( 96 _listening, 97 # Make sure that this listen failure is reported promptly and with 98 # the creation stack. 99 err, 100 errbackArgs=( 101 "Listener created at {}".format( 102 "".join(format_list(self._creation_stack)), 103 ), 104 ), 105 ) 106 Listener.__init__ = _listener__init__ 107 Listener.startService = _listener_startService 108 logging_for_pb_listener() 55 109 56 110 import sys -
TabularUnified src/allmydata/test/test_client.py ¶
r8df1ed1b rc385e95 38 38 39 39 import allmydata 40 import allmydata.frontends.magic_folder41 40 import allmydata.util.log 42 41 43 from allmydata.node import OldConfigError, OldConfigOptionError,UnescapedHashError, _Config, create_node_dir42 from allmydata.node import OldConfigError, UnescapedHashError, _Config, create_node_dir 44 43 from allmydata.frontends.auth import NeedRootcapLookupScheme 45 44 from allmydata.version_checks import ( … … 659 658 yield _check("helper.furl = pb://blah\n", "pb://blah") 660 659 661 @defer.inlineCallbacks662 def test_create_magic_folder_service(self):663 """664 providing magic-folder options actually creates a MagicFolder service665 """666 boom = False667 class Boom(Exception):668 pass669 670 class MockMagicFolder(allmydata.frontends.magic_folder.MagicFolder):671 name = 'magic-folder'672 673 def __init__(self, client, upload_dircap, collective_dircap, local_path_u, dbfile, umask, name,674 inotify=None, uploader_delay=1.0, clock=None, downloader_delay=3):675 if boom:676 raise Boom()677 678 service.MultiService.__init__(self)679 self.client = client680 self._umask = umask681 self.upload_dircap = upload_dircap682 self.collective_dircap = collective_dircap683 self.local_dir = local_path_u684 self.dbfile = dbfile685 self.inotify = inotify686 687 def startService(self):688 self.running = True689 690 def stopService(self):691 self.running = False692 693 def ready(self):694 pass695 696 self.patch(allmydata.frontends.magic_folder, 'MagicFolder', MockMagicFolder)697 698 upload_dircap = "URI:DIR2:blah"699 local_dir_u = self.unicode_or_fallback(u"loc\u0101l_dir", u"local_dir")700 local_dir_utf8 = local_dir_u.encode('utf-8')701 config = (BASECONFIG +702 "[storage]\n" +703 "enabled = false\n" +704 "[magic_folder]\n" +705 "enabled = true\n")706 707 basedir1 = "test_client.Basic.test_create_magic_folder_service1"708 os.mkdir(basedir1)709 os.mkdir(local_dir_u)710 711 # which config-entry should be missing?712 fileutil.write(os.path.join(basedir1, "tahoe.cfg"),713 config + "local.directory = " + local_dir_utf8 + "\n")714 with self.assertRaises(IOError):715 yield client.create_client(basedir1)716 717 # local.directory entry missing .. but that won't be an error718 # now, it'll just assume there are not magic folders719 # .. hrm...should we make that an error (if enabled=true but720 # there's not yaml AND no local.directory?)721 fileutil.write(os.path.join(basedir1, "tahoe.cfg"), config)722 fileutil.write(os.path.join(basedir1, "private", "magic_folder_dircap"), "URI:DIR2:blah")723 fileutil.write(os.path.join(basedir1, "private", "collective_dircap"), "URI:DIR2:meow")724 725 fileutil.write(os.path.join(basedir1, "tahoe.cfg"),726 config.replace("[magic_folder]\n", "[drop_upload]\n"))727 728 with self.assertRaises(OldConfigOptionError):729 yield client.create_client(basedir1)730 731 fileutil.write(os.path.join(basedir1, "tahoe.cfg"),732 config + "local.directory = " + local_dir_utf8 + "\n")733 c1 = yield client.create_client(basedir1)734 magicfolder = c1.getServiceNamed('magic-folder')735 self.failUnless(isinstance(magicfolder, MockMagicFolder), magicfolder)736 self.failUnlessReallyEqual(magicfolder.client, c1)737 self.failUnlessReallyEqual(magicfolder.upload_dircap, upload_dircap)738 self.failUnlessReallyEqual(os.path.basename(magicfolder.local_dir), local_dir_u)739 self.failUnless(magicfolder.inotify is None, magicfolder.inotify)740 # It doesn't start until the client starts.741 self.assertFalse(magicfolder.running)742 743 # See above.744 boom = True745 746 basedir2 = "test_client.Basic.test_create_magic_folder_service2"747 os.mkdir(basedir2)748 os.mkdir(os.path.join(basedir2, "private"))749 fileutil.write(os.path.join(basedir2, "tahoe.cfg"),750 BASECONFIG +751 "[magic_folder]\n" +752 "enabled = true\n" +753 "local.directory = " + local_dir_utf8 + "\n")754 fileutil.write(os.path.join(basedir2, "private", "magic_folder_dircap"), "URI:DIR2:blah")755 fileutil.write(os.path.join(basedir2, "private", "collective_dircap"), "URI:DIR2:meow")756 with self.assertRaises(Boom):757 yield client.create_client(basedir2)758 759 660 760 661 def flush_but_dont_ignore(res): -
TabularUnified src/allmydata/test/test_python2_regressions.py ¶
r8df1ed1b rc385e95 16 16 BLACKLIST = { 17 17 "allmydata.test.check_load", 18 "allmydata.watchdog._watchdog_541",19 "allmydata.watchdog.inotify",20 "allmydata.windows.inotify",21 18 "allmydata.windows.registry", 22 19 } -
TabularUnified src/allmydata/test/test_websocket_logs.py ¶
r8df1ed1b rc385e95 41 41 proto.on("message", got_message) 42 42 43 @log_call(action_type=u"test:cli: magic-folder:cleanup")43 @log_call(action_type=u"test:cli:some-exciting-action") 44 44 def do_a_thing(): 45 45 pass -
TabularUnified src/allmydata/test/web/test_web.py ¶
r8df1ed1b rc385e95 4 4 import json 5 5 import treq 6 import mock7 6 8 7 from bs4 import BeautifulSoup … … 35 34 from allmydata.dirnode import DirectoryNode 36 35 from allmydata.nodemaker import NodeMaker 37 from allmydata.frontends.magic_folder import QueuedItem38 36 from allmydata.web import status 39 37 from allmydata.web.common import WebError, MultiFormatPage … … 67 65 from allmydata.client import _Client, SecretHolder 68 66 from .common import unknown_rwcap, unknown_rocap, unknown_immcap, FAVICON_MARKUP 69 from ..status import FakeStatus70 67 71 68 # create a fake uploader/downloader, and a couple of fake dirnodes, then … … 128 125 129 126 130 def create_test_queued_item(relpath_u, history=[]):131 progress = mock.Mock()132 progress.progress = 100.0133 item = QueuedItem(relpath_u, progress, 1234)134 for the_status, timestamp in history:135 item.set_status(the_status, current_time=timestamp)136 return item137 138 139 class FakeMagicFolder(object):140 def __init__(self):141 self.uploader = FakeStatus()142 self.downloader = FakeStatus()143 144 def get_public_status(self):145 return (146 True,147 [148 'a magic-folder status message'149 ],150 )151 152 153 127 def build_one_ds(): 154 128 ds = DownloadStatus("storage_index", 1234) … … 285 259 # minimal subset 286 260 service.MultiService.__init__(self) 287 self._magic_folders = dict()288 261 self.all_contents = {} 289 262 self.nodeid = "fake_nodeid" … … 941 914 d.addCallback(_check) 942 915 return d 943 944 @defer.inlineCallbacks945 def test_magicfolder_status_bad_token(self):946 with self.assertRaises(Error):947 yield self.POST(948 '/magic_folder?t=json',949 t='json',950 name='default',951 token='not the token you are looking for',952 )953 954 @defer.inlineCallbacks955 def test_magicfolder_status_wrong_folder(self):956 with self.assertRaises(Exception) as ctx:957 yield self.POST(958 '/magic_folder?t=json',959 t='json',960 name='a non-existent magic-folder',961 token=self.s.get_auth_token(),962 )963 self.assertIn(964 "Not Found",965 str(ctx.exception)966 )967 968 @defer.inlineCallbacks969 def test_magicfolder_status_success(self):970 self.s._magic_folders['default'] = mf = FakeMagicFolder()971 mf.uploader.status = [972 create_test_queued_item(u"rel/uppath", [('done', 12345)])973 ]974 mf.downloader.status = [975 create_test_queued_item(u"rel/downpath", [('done', 23456)])976 ]977 data = yield self.POST(978 '/magic_folder?t=json',979 t='json',980 name='default',981 token=self.s.get_auth_token(),982 )983 data = json.loads(data)984 self.assertEqual(985 data,986 [987 {988 "status": "done",989 "path": "rel/uppath",990 "kind": "upload",991 "percent_done": 100.0,992 "done_at": 12345,993 "size": 1234,994 },995 {996 "status": "done",997 "path": "rel/downpath",998 "kind": "download",999 "percent_done": 100.0,1000 "done_at": 23456,1001 "size": 1234,1002 },1003 ]1004 )1005 1006 @defer.inlineCallbacks1007 def test_magicfolder_root_success(self):1008 self.s._magic_folders['default'] = mf = FakeMagicFolder()1009 mf.uploader.status = [1010 create_test_queued_item(u"rel/path", [('done', 12345)])1011 ]1012 data = yield self.GET(1013 '/',1014 )1015 del data1016 916 1017 917 def test_status(self): -
TabularUnified src/allmydata/util/eliotutil.py ¶
r8df1ed1b rc385e95 17 17 "validateInstanceOf", 18 18 "validateSetMembership", 19 "MAYBE_NOTIFY",20 "CALLBACK",21 "INOTIFY_EVENTS",22 "RELPATH",23 "VERSION",24 "LAST_UPLOADED_URI",25 "LAST_DOWNLOADED_URI",26 "LAST_DOWNLOADED_TIMESTAMP",27 "PATHINFO",28 19 ] 29 20 … … 52 43 ILogger, 53 44 Message, 54 Field,55 ActionType,56 45 FileDestination, 57 46 add_destinations, … … 87 76 from twisted.application.service import Service 88 77 89 90 from .fileutil import (91 PathInfo,92 )93 from .fake_inotify import (94 humanReadableMask,95 )96 97 78 def validateInstanceOf(t): 98 79 """ … … 112 93 raise ValidationError("{} not in {}".format(v, s)) 113 94 return validator 114 115 RELPATH = Field.for_types(116 u"relpath",117 [unicode],118 u"The relative path of a file in a magic-folder.",119 )120 121 VERSION = Field.for_types(122 u"version",123 [int, long],124 u"The version of the file.",125 )126 127 LAST_UPLOADED_URI = Field.for_types(128 u"last_uploaded_uri",129 [unicode, bytes, None],130 u"The filecap to which this version of this file was uploaded.",131 )132 133 LAST_DOWNLOADED_URI = Field.for_types(134 u"last_downloaded_uri",135 [unicode, bytes, None],136 u"The filecap from which the previous version of this file was downloaded.",137 )138 139 LAST_DOWNLOADED_TIMESTAMP = Field.for_types(140 u"last_downloaded_timestamp",141 [float, int, long],142 u"(XXX probably not really, don't trust this) The timestamp of the last download of this file.",143 )144 145 PATHINFO = Field(146 u"pathinfo",147 lambda v: None if v is None else {148 "isdir": v.isdir,149 "isfile": v.isfile,150 "islink": v.islink,151 "exists": v.exists,152 "size": v.size,153 "mtime_ns": v.mtime_ns,154 "ctime_ns": v.ctime_ns,155 },156 u"The metadata for this version of this file.",157 validateInstanceOf((type(None), PathInfo)),158 )159 160 INOTIFY_EVENTS = Field(161 u"inotify_events",162 humanReadableMask,163 u"Details about a filesystem event generating a notification event.",164 validateInstanceOf((int, long)),165 )166 167 MAYBE_NOTIFY = ActionType(168 u"filesystem:notification:maybe-notify",169 [],170 [],171 u"A filesystem event is being considered for dispatch to an application handler.",172 )173 174 CALLBACK = ActionType(175 u"filesystem:notification:callback",176 [INOTIFY_EVENTS],177 [],178 u"A filesystem event is being dispatched to an application callback."179 )180 95 181 96 def eliot_logging_service(reactor, destinations): -
TabularUnified src/allmydata/web/root.py ¶
r8df1ed1b rc385e95 22 22 from allmydata.interfaces import IFileNode 23 23 from allmydata.web import filenode, directory, unlinked, status 24 from allmydata.web import storage , magic_folder24 from allmydata.web import storage 25 25 from allmydata.web.common import ( 26 26 abbreviate_size, … … 209 209 self.putChild("cap", URIHandler(client)) 210 210 211 # handler for "/magic_folder" URIs212 self.putChild("magic_folder", magic_folder.MagicFolderWebApi(client))213 214 211 # Handler for everything beneath "/private", an area of the resource 215 212 # hierarchy which is only accessible with the private per-node API … … 307 304 return description 308 305 309 310 def data_magic_folders(self, ctx, data):311 return self.client._magic_folders.keys()312 313 def render_magic_folder_row(self, ctx, data):314 magic_folder = self.client._magic_folders[data]315 (ok, messages) = magic_folder.get_public_status()316 ctx.fillSlots("magic_folder_name", data)317 if ok:318 ctx.fillSlots("magic_folder_status", "yes")319 ctx.fillSlots("magic_folder_status_alt", "working")320 else:321 ctx.fillSlots("magic_folder_status", "no")322 ctx.fillSlots("magic_folder_status_alt", "not working")323 324 status = T.ul(class_="magic-folder-status")325 for msg in messages:326 status[T.li[str(msg)]]327 return ctx.tag[status]328 329 def render_magic_folder(self, ctx, data):330 if not self.client._magic_folders:331 return T.p()332 return ctx.tag333 306 334 307 def render_services(self, ctx, data): -
TabularUnified src/allmydata/web/static/css/new-tahoe.css ¶
r8df1ed1b rc385e95 54 54 } 55 55 56 .magic-folder-status {57 clear: left;58 margin-left: 40px; /* width of status-indicator + margins */59 }60 61 56 .furl { 62 57 font-size: 0.8em; -
TabularUnified src/allmydata/web/statistics.xhtml ¶
r8df1ed1b rc385e95 1 <html xmlns: n="http://nevow.com/ns/nevow/0.1">1 <html xmlns:t="http://twistedmatrix.com/ns/twisted.web.template/0.1"> 2 2 <head> 3 3 <title>Tahoe-LAFS - Operational Statistics</title> … … 6 6 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 7 7 </head> 8 <body n:data="get_stats">8 <body> 9 9 10 10 <h1>Operational Statistics</h1> … … 12 12 <h2>General</h2> 13 13 14 <ul> 15 <li>Load Average: <span n:render="load_average" /></li> 16 <li>Peak Load: <span n:render="peak_load" /></li> 17 <li>Files Uploaded (immutable): <span n:render="uploads" /></li> 18 <li>Files Downloaded (immutable): <span n:render="downloads" /></li> 19 <li>Files Published (mutable): <span n:render="publishes" /></li> 20 <li>Files Retrieved (mutable): <span n:render="retrieves" /></li> 21 </ul> 22 23 <h2>Magic Folder</h2> 24 25 <ul> 26 <li>Local Directories Monitored: <span n:render="magic_uploader_monitored" /></li> 27 <li>Files Uploaded: <span n:render="magic_uploader_succeeded" /></li> 28 <li>Files Queued for Upload: <span n:render="magic_uploader_queued" /></li> 29 <li>Failed Uploads: <span n:render="magic_uploader_failed" /></li> 30 <li>Files Downloaded: <span n:render="magic_downloader_succeeded" /></li> 31 <li>Files Queued for Download: <span n:render="magic_downloader_queued" /></li> 32 <li>Failed Downloads: <span n:render="magic_downloader_failed" /></li> 33 </ul> 14 <ul> 15 <li>Load Average: <t:transparent t:render="load_average" /></li> 16 <li>Peak Load: <t:transparent t:render="peak_load" /></li> 17 <li>Files Uploaded (immutable): <t:transparent t:render="uploads" /></li> 18 <li>Files Downloaded (immutable): <t:transparent t:render="downloads" /></li> 19 <li>Files Published (mutable): <t:transparent t:render="publishes" /></li> 20 <li>Files Retrieved (mutable): <t:transparent t:render="retrieves" /></li> 21 </ul> 34 22 35 23 <h2>Raw Stats:</h2> 36 <pre n:render="raw" />24 <pre t:render="raw" /> 37 25 38 26 <div>Return to the <a href="/">Welcome Page</a></div> -
TabularUnified src/allmydata/web/status.py ¶
r8df1ed1b rc385e95 3 3 import json 4 4 from twisted.internet import defer 5 from twisted.python.filepath import FilePath 5 6 from twisted.web.resource import Resource 7 from twisted.web.template import ( 8 Element, 9 XMLFile, 10 renderer, 11 renderElement, 12 ) 6 13 from nevow import rend, tags as T 7 14 from allmydata.util import base32, idlib … … 15 22 render_time, 16 23 MultiFormatPage, 24 MultiFormatResource, 17 25 ) 18 26 from allmydata.interfaces import IUploadStatus, IDownloadStatus, \ … … 1165 1173 return str(data["chk_upload_helper.encoded_bytes"]) 1166 1174 1167 1168 class Statistics(MultiFormatPage): 1169 docFactory = getxmlfile("statistics.xhtml") 1175 # Render "/statistics" page. 1176 class Statistics(MultiFormatResource): 1177 """Class that renders "/statistics" page. 1178 1179 :param _allmydata.stats.StatsProvider provider: node statistics 1180 provider. 1181 """ 1170 1182 1171 1183 def __init__(self, provider): 1172 rend.Page.__init__(self, provider) 1173 self.provider = provider 1184 super(Statistics, self).__init__() 1185 self._provider = provider 1186 1187 def render_HTML(self, req): 1188 return renderElement(req, StatisticsElement(self._provider)) 1174 1189 1175 1190 def render_JSON(self, req): 1176 stats = self. provider.get_stats()1191 stats = self._provider.get_stats() 1177 1192 req.setHeader("content-type", "text/plain") 1178 1193 return json.dumps(stats, indent=1) + "\n" 1179 1194 1180 def data_get_stats(self, ctx, data): 1181 return self.provider.get_stats() 1182 1183 def render_load_average(self, ctx, data): 1184 return str(data["stats"].get("load_monitor.avg_load")) 1185 1186 def render_peak_load(self, ctx, data): 1187 return str(data["stats"].get("load_monitor.max_load")) 1188 1189 def render_uploads(self, ctx, data): 1190 files = data["counters"].get("uploader.files_uploaded", 0) 1191 bytes = data["counters"].get("uploader.bytes_uploaded", 0) 1192 return ("%s files / %s bytes (%s)" % 1193 (files, bytes, abbreviate_size(bytes))) 1194 1195 def render_downloads(self, ctx, data): 1196 files = data["counters"].get("downloader.files_downloaded", 0) 1197 bytes = data["counters"].get("downloader.bytes_downloaded", 0) 1198 return ("%s files / %s bytes (%s)" % 1199 (files, bytes, abbreviate_size(bytes))) 1200 1201 def render_publishes(self, ctx, data): 1202 files = data["counters"].get("mutable.files_published", 0) 1203 bytes = data["counters"].get("mutable.bytes_published", 0) 1204 return "%s files / %s bytes (%s)" % (files, bytes, 1205 abbreviate_size(bytes)) 1206 1207 def render_retrieves(self, ctx, data): 1208 files = data["counters"].get("mutable.files_retrieved", 0) 1209 bytes = data["counters"].get("mutable.bytes_retrieved", 0) 1210 return "%s files / %s bytes (%s)" % (files, bytes, 1211 abbreviate_size(bytes)) 1212 1213 def render_magic_uploader_monitored(self, ctx, data): 1214 dirs = data["counters"].get("magic_folder.uploader.dirs_monitored", 0) 1215 return "%s directories" % (dirs,) 1216 1217 def render_magic_uploader_succeeded(self, ctx, data): 1218 # TODO: bytes uploaded 1219 files = data["counters"].get("magic_folder.uploader.objects_succeeded", 0) 1220 return "%s files" % (files,) 1221 1222 def render_magic_uploader_queued(self, ctx, data): 1223 files = data["counters"].get("magic_folder.uploader.objects_queued", 0) 1224 return "%s files" % (files,) 1225 1226 def render_magic_uploader_failed(self, ctx, data): 1227 files = data["counters"].get("magic_folder.uploader.objects_failed", 0) 1228 return "%s files" % (files,) 1229 1230 def render_magic_downloader_succeeded(self, ctx, data): 1231 # TODO: bytes uploaded 1232 files = data["counters"].get("magic_folder.downloader.objects_succeeded", 0) 1233 return "%s files" % (files,) 1234 1235 def render_magic_downloader_queued(self, ctx, data): 1236 files = data["counters"].get("magic_folder.downloader.objects_queued", 0) 1237 return "%s files" % (files,) 1238 1239 def render_magic_downloader_failed(self, ctx, data): 1240 files = data["counters"].get("magic_folder.downloader.objects_failed", 0) 1241 return "%s files" % (files,) 1242 1243 def render_raw(self, ctx, data): 1244 raw = pprint.pformat(data) 1245 return ctx.tag[raw] 1195 class StatisticsElement(Element): 1196 1197 loader = XMLFile(FilePath(__file__).sibling("statistics.xhtml")) 1198 1199 def __init__(self, provider): 1200 super(StatisticsElement, self).__init__() 1201 # provider.get_stats() returns a dict of the below form, for 1202 # example (there's often more data than this): 1203 # 1204 # { 1205 # 'stats': { 1206 # 'storage_server.disk_used': 809601609728, 1207 # 'storage_server.accepting_immutable_shares': 1, 1208 # 'storage_server.disk_free_for_root': 131486851072, 1209 # 'storage_server.reserved_space': 1000000000, 1210 # 'node.uptime': 0.16520118713378906, 1211 # 'storage_server.disk_total': 941088460800, 1212 # 'cpu_monitor.total': 0.004513999999999907, 1213 # 'storage_server.disk_avail': 82610759168, 1214 # 'storage_server.allocated': 0, 1215 # 'storage_server.disk_free_for_nonroot': 83610759168 }, 1216 # 'counters': { 1217 # 'uploader.files_uploaded': 0, 1218 # 'uploader.bytes_uploaded': 0, 1219 # ... } 1220 # } 1221 # 1222 # Note that `counters` can be empty. 1223 self._stats = provider.get_stats() 1224 1225 @renderer 1226 def load_average(self, req, tag): 1227 return tag(str(self._stats["stats"].get("load_monitor.avg_load"))) 1228 1229 @renderer 1230 def peak_load(self, req, tag): 1231 return tag(str(self._stats["stats"].get("load_monitor.max_load"))) 1232 1233 @renderer 1234 def uploads(self, req, tag): 1235 files = self._stats["counters"].get("uploader.files_uploaded", 0) 1236 bytes = self._stats["counters"].get("uploader.bytes_uploaded", 0) 1237 return tag(("%s files / %s bytes (%s)" % 1238 (files, bytes, abbreviate_size(bytes)))) 1239 1240 @renderer 1241 def downloads(self, req, tag): 1242 files = self._stats["counters"].get("downloader.files_downloaded", 0) 1243 bytes = self._stats["counters"].get("downloader.bytes_downloaded", 0) 1244 return tag("%s files / %s bytes (%s)" % 1245 (files, bytes, abbreviate_size(bytes))) 1246 1247 @renderer 1248 def publishes(self, req, tag): 1249 files = self._stats["counters"].get("mutable.files_published", 0) 1250 bytes = self._stats["counters"].get("mutable.bytes_published", 0) 1251 return tag("%s files / %s bytes (%s)" % (files, bytes, 1252 abbreviate_size(bytes))) 1253 1254 @renderer 1255 def retrieves(self, req, tag): 1256 files = self._stats["counters"].get("mutable.files_retrieved", 0) 1257 bytes = self._stats["counters"].get("mutable.bytes_retrieved", 0) 1258 return tag("%s files / %s bytes (%s)" % (files, bytes, 1259 abbreviate_size(bytes))) 1260 1261 @renderer 1262 def raw(self, req, tag): 1263 raw = pprint.pformat(self._stats) 1264 return tag(raw) -
TabularUnified src/allmydata/web/welcome.xhtml ¶
r8df1ed1b rc385e95 160 160 </div> 161 161 162 <div n:render="magic_folder" class="row-fluid">163 <h2>Magic Folders</h2>164 <div n:render="sequence" n:data="magic_folders">165 <div n:pattern="item" n:render="magic_folder_row"><div class="status-indicator"><img><n:attr name="src">img/connected-<n:slot name="magic_folder_status" />.png</n:attr><n:attr name="alt"><n:slot name="magic_folder_status_alt" /></n:attr></img></div><h3><n:slot name="magic_folder_name" /></h3></div>166 </div>167 </div><!--/row-->168 169 162 <div class="row-fluid"> 170 163 <h2>
Note: See TracChangeset
for help on using the changeset viewer.