Changeset c385e95 in trunk


Ignore:
Timestamp:
2020-04-23T18:24:00Z (5 years ago)
Author:
meejah <meejah@…>
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.
Message:

Merge branch 'master' into ticket3252-port-web-directory.remaining.1

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-jessie
     1FROM pypy:2.7-buster
    22
    33ENV WHEELHOUSE_PATH /tmp/wheelhouse
  • TabularUnified .circleci/config.yml

    r8df1ed1b rc385e95  
    2828      - "nixos-19.09"
    2929
    30       # Test against PyPy 2.7/7.1.1
    31       - "pypy2.7-7.1"
     30      # Test against PyPy 2.7
     31      - "pypy2.7-buster"
    3232
    3333      # Other assorted tasks and configurations
     
    7070      - "build-image-centos-8"
    7171      - "build-image-slackware-14.2"
    72       - "build-image-pypy-2.7-7.1.1-jessie"
     72      - "build-image-pypy-2.7-buster"
    7373
    7474
     
    199199
    200200
    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"
    205205        user: "nobody"
    206206
     
    514514
    515515
    516   build-image-pypy-2.7-7.1.1-jessie:
     516  build-image-pypy-2.7-buster:
    517517    <<: *BUILD_IMAGE
    518518
    519519    environment:
    520520      DISTRO: "pypy"
    521       TAG: "2.7-7.1.1-jessie"
     521      TAG: "2.7-buster"
  • TabularUnified .github/workflows/ci.yml

    r8df1ed1b rc385e95  
    33on:
    44  push:
     5    branches:
     6      - "master"
    57  pull_request:
    68
  • TabularUnified .gitignore

    r8df1ed1b rc385e95  
    4444/docs/_build/
    4545/coverage.xml
    46 /smoke_magicfolder/
    4746/.hypothesis/
    4847
  • TabularUnified Makefile

    r8df1ed1b rc385e95  
    4242        #   echo not uploading tahoe-lafs-osx-pkg because this is not trunk but is branch \"${BB_BRANCH}\" ; \
    4343        # fi
    44 
    45 .PHONY: smoketest
    46 smoketest:
    47         -python ./src/allmydata/test/check_magicfolder_smoke.py kill
    48         -rm -rf smoke_magicfolder/
    49         python ./src/allmydata/test/check_magicfolder_smoke.py
    5044
    5145# code coverage-based testing is disabled temporarily, as we switch to tox.
  • TabularUnified docs/configuration.rst

    r8df1ed1b rc385e95  
    8383* SFTP service
    8484* FTP service
    85 * Magic Folder service
    8685* helper service
    8786* storage service.
     
    719718    for instructions on configuring these services, and the ``[sftpd]`` and
    720719    ``[ftpd]`` sections of ``tahoe.cfg``.
    721 
    722 Magic Folder
    723 
    724     A node running on Linux or Windows can be configured to automatically
    725     upload files that are created or changed in a specified local directory.
    726     See :doc:`frontends/magic-folder` for details.
    727720
    728721
  • TabularUnified docs/index.rst

    r8df1ed1b rc385e95  
    2121   frontends/webapi
    2222   frontends/FTP-and-SFTP
    23    frontends/magic-folder
    2423   frontends/download-status
    2524
     
    3837   cautions
    3938   write_coordination
    40    magic-folder-howto
    4139   backupdb
    4240
  • TabularUnified docs/magic-wormhole-invites.rst

    r8df1ed1b rc385e95  
    2020Inside Tahoe-LAFS we are using a channel created using `magic
    2121wormhole`_ 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.
     22Introducer with new clients.
    2523
    2624This is a two-part process. Alice runs a grid and wishes to have her
  • TabularUnified docs/proposed/index.rst

    r8df1ed1b rc385e95  
    1515
    1616   leasedb
    17    magic-folder/filesystem-integration
    18    magic-folder/remote-to-local-sync
    19    magic-folder/user-interface-design
    20    magic-folder/multi-party-conflict-detection
    2117   http-storage-node-protocol
  • TabularUnified integration/conftest.py

    r8df1ed1b rc385e95  
    333333@log_call(action_type=u"integration:alice", include_args=[], include_result=False)
    334334def alice(reactor, temp_dir, introducer_furl, flog_gatherer, storage_nodes, request):
    335     try:
    336         mkdir(join(temp_dir, 'magic-alice'))
    337     except OSError:
    338         pass
    339 
    340335    process = pytest_twisted.blockon(
    341336        _create_node(
     
    352347@log_call(action_type=u"integration:bob", include_args=[], include_result=False)
    353348def bob(reactor, temp_dir, introducer_furl, flog_gatherer, storage_nodes, request):
    354     try:
    355         mkdir(join(temp_dir, 'magic-bob'))
    356     except OSError:
    357         pass
    358 
    359349    process = pytest_twisted.blockon(
    360350        _create_node(
     
    366356    await_client_ready(process)
    367357    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, the
    377         # storage servers aren't "really" ready to roll yet (uploads fairly
    378         # 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 is
    410         # 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             pass
    416         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 invite
    421 
    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 is
    447     # 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         pass
    454 
    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'))
    459358
    460359
  • TabularUnified integration/test_aaa_aardvark.py

    r8df1ed1b rc385e95  
    1717def test_create_storage(storage_nodes):
    1818    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  
    1515import util
    1616
    17 # see "conftest.py" for the fixtures (e.g. "magic_folder")
     17# see "conftest.py" for the fixtures (e.g. "tor_network")
    1818
    1919@pytest_twisted.inlineCallbacks
  • TabularUnified integration/util.py

    r8df1ed1b rc385e95  
    499499        )
    500500    )
    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  
    1313    substituteInPlace setup.py \
    1414      --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  '';
    1716
    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;
    2322
    2423  checkInputs = [ testtools pytest hypothesis ];
  • TabularUnified setup.py

    r8df1ed1b rc385e95  
    6363    "cryptography >= 2.6",
    6464
    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
    6866    #   Twisted's FTP server to support asynchronous close.
    6967    # * The SFTP frontend depends on Twisted 11.0.0 to fix the SSH server
     
    355353          # discussion.
    356354          ':sys_platform=="win32"': ["pywin32 != 226"],
    357           ':sys_platform!="win32" and sys_platform!="linux2"': ["watchdog"],  # For magic-folder on "darwin" (macOS) and the BSDs
    358355          "test": [
    359356              # Pin a specific pyflakes so we don't have different folks
  • TabularUnified src/allmydata/client.py

    r8df1ed1b rc385e95  
    8585            "stats_gatherer.furl",
    8686            "storage.plugins",
    87         ),
    88         "drop_upload": (  # deprecated already?
    89             "enabled",
    9087        ),
    9188        "ftpd": (
     
    122119            "enabled",
    123120        ),
    124         "magic_folder": (
    125             "download.umask",
    126             "enabled",
    127             "local.directory",
    128             "poll_interval",
    129         ),
    130121    },
    131122    is_valid_section=_is_valid_section,
     
    682673        node.Node.__init__(self, config, main_tub, control_tub, i2p_provider, tor_provider)
    683674
    684         self._magic_folders = dict()
    685675        self.started_timestamp = time.time()
    686676        self.logSource = "Client"
     
    708698        self.init_ftp_server()
    709699        self.init_sftp_server()
    710         self.init_magic_folder()
    711700
    712701        # If the node sees an exit_trigger file, it will poll every second to see
     
    969958        random data in "api_auth_token" which must be echoed to API
    970959        calls.
    971 
    972         Currently only the URI '/magic' for magic-folder status; other
    973         endpoints are invited to include this as well, as appropriate.
    974960        """
    975961        return self.config.get_private_config('api_auth_token')
     
    10891075            s.setServiceParent(self)
    10901076
    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_folder
    1101 
    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                 raise
    1107 
    1108             # start processing the upload queue when we've connected to
    1109             # enough servers
    1110             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] = s
    1117                 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 ignore
    1122                     return None
    1123                 connected_d.addCallback(connected_enough, s)
    1124 
    11251077    def _check_exit_trigger(self, exit_trigger_file):
    11261078        if os.path.exists(exit_trigger_file):
  • TabularUnified src/allmydata/scripts/runner.py

    r8df1ed1b rc385e95  
    1010from allmydata.scripts.common import get_default_nodedir
    1111from 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, \
    1313    tahoe_stop, tahoe_restart, tahoe_run, tahoe_invite
    1414from allmydata.util.encodingutil import quote_output, quote_local_unicode_path, get_io_encoding
     
    6262                    +   debug.subCommands
    6363                    +   cli.subCommands
    64                     +   magic_folder_cli.subCommands
    6564                    +   tahoe_invite.subCommands
    6665                    )
     
    155154        f0 = cli.dispatch[command]
    156155        f = lambda so: threads.deferToThread(f0, so)
    157     elif command in magic_folder_cli.dispatch:
    158         # same
    159         f0 = magic_folder_cli.dispatch[command]
    160         f = lambda so: threads.deferToThread(f0, so)
    161156    elif command in tahoe_invite.dispatch:
    162157        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"""
     11Some setup that should apply across the entire test suite.
     12
     13Rather than defining interesting APIs for other code to use, this just causes
     14some side-effects which make things better when the test suite runs.
     15"""
     16
     17from traceback import extract_stack, format_list
     18from foolscap.pb import Listener
     19from twisted.python.log import err
     20from twisted.application import service
     21
    122
    223from foolscap.logging.incident import IncidentQualifier
     
    5374_configure_hypothesis()
    5475
     76def 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
     108logging_for_pb_listener()
    55109
    56110import sys
  • TabularUnified src/allmydata/test/test_client.py

    r8df1ed1b rc385e95  
    3838
    3939import allmydata
    40 import allmydata.frontends.magic_folder
    4140import allmydata.util.log
    4241
    43 from allmydata.node import OldConfigError, OldConfigOptionError, UnescapedHashError, _Config, create_node_dir
     42from allmydata.node import OldConfigError, UnescapedHashError, _Config, create_node_dir
    4443from allmydata.frontends.auth import NeedRootcapLookupScheme
    4544from allmydata.version_checks import (
     
    659658        yield _check("helper.furl = pb://blah\n", "pb://blah")
    660659
    661     @defer.inlineCallbacks
    662     def test_create_magic_folder_service(self):
    663         """
    664         providing magic-folder options actually creates a MagicFolder service
    665         """
    666         boom = False
    667         class Boom(Exception):
    668             pass
    669 
    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 = client
    680                 self._umask = umask
    681                 self.upload_dircap = upload_dircap
    682                 self.collective_dircap = collective_dircap
    683                 self.local_dir = local_path_u
    684                 self.dbfile = dbfile
    685                 self.inotify = inotify
    686 
    687             def startService(self):
    688                 self.running = True
    689 
    690             def stopService(self):
    691                 self.running = False
    692 
    693             def ready(self):
    694                 pass
    695 
    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 error
    718         # now, it'll just assume there are not magic folders
    719         # .. hrm...should we make that an error (if enabled=true but
    720         # 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 = True
    745 
    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 
    759660
    760661def flush_but_dont_ignore(res):
  • TabularUnified src/allmydata/test/test_python2_regressions.py

    r8df1ed1b rc385e95  
    1616BLACKLIST = {
    1717    "allmydata.test.check_load",
    18     "allmydata.watchdog._watchdog_541",
    19     "allmydata.watchdog.inotify",
    20     "allmydata.windows.inotify",
    2118    "allmydata.windows.registry",
    2219}
  • TabularUnified src/allmydata/test/test_websocket_logs.py

    r8df1ed1b rc385e95  
    4141        proto.on("message", got_message)
    4242
    43         @log_call(action_type=u"test:cli:magic-folder:cleanup")
     43        @log_call(action_type=u"test:cli:some-exciting-action")
    4444        def do_a_thing():
    4545            pass
  • TabularUnified src/allmydata/test/web/test_web.py

    r8df1ed1b rc385e95  
    44import json
    55import treq
    6 import mock
    76
    87from bs4 import BeautifulSoup
     
    3534from allmydata.dirnode import DirectoryNode
    3635from allmydata.nodemaker import NodeMaker
    37 from allmydata.frontends.magic_folder import QueuedItem
    3836from allmydata.web import status
    3937from allmydata.web.common import WebError, MultiFormatPage
     
    6765from allmydata.client import _Client, SecretHolder
    6866from .common import unknown_rwcap, unknown_rocap, unknown_immcap, FAVICON_MARKUP
    69 from ..status import FakeStatus
    7067
    7168# create a fake uploader/downloader, and a couple of fake dirnodes, then
     
    128125
    129126
    130 def create_test_queued_item(relpath_u, history=[]):
    131     progress = mock.Mock()
    132     progress.progress = 100.0
    133     item = QueuedItem(relpath_u, progress, 1234)
    134     for the_status, timestamp in history:
    135         item.set_status(the_status, current_time=timestamp)
    136     return item
    137 
    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 
    153127def build_one_ds():
    154128    ds = DownloadStatus("storage_index", 1234)
     
    285259        # minimal subset
    286260        service.MultiService.__init__(self)
    287         self._magic_folders = dict()
    288261        self.all_contents = {}
    289262        self.nodeid = "fake_nodeid"
     
    941914        d.addCallback(_check)
    942915        return d
    943 
    944     @defer.inlineCallbacks
    945     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.inlineCallbacks
    955     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.inlineCallbacks
    969     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.inlineCallbacks
    1007     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 data
    1016916
    1017917    def test_status(self):
  • TabularUnified src/allmydata/util/eliotutil.py

    r8df1ed1b rc385e95  
    1717    "validateInstanceOf",
    1818    "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",
    2819]
    2920
     
    5243    ILogger,
    5344    Message,
    54     Field,
    55     ActionType,
    5645    FileDestination,
    5746    add_destinations,
     
    8776from twisted.application.service import Service
    8877
    89 
    90 from .fileutil import (
    91     PathInfo,
    92 )
    93 from .fake_inotify import (
    94     humanReadableMask,
    95 )
    96 
    9778def validateInstanceOf(t):
    9879    """
     
    11293            raise ValidationError("{} not in {}".format(v, s))
    11394    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 )
    18095
    18196def eliot_logging_service(reactor, destinations):
  • TabularUnified src/allmydata/web/root.py

    r8df1ed1b rc385e95  
    2222from allmydata.interfaces import IFileNode
    2323from allmydata.web import filenode, directory, unlinked, status
    24 from allmydata.web import storage, magic_folder
     24from allmydata.web import storage
    2525from allmydata.web.common import (
    2626    abbreviate_size,
     
    209209        self.putChild("cap", URIHandler(client))
    210210
    211         # handler for "/magic_folder" URIs
    212         self.putChild("magic_folder", magic_folder.MagicFolderWebApi(client))
    213 
    214211        # Handler for everything beneath "/private", an area of the resource
    215212        # hierarchy which is only accessible with the private per-node API
     
    307304        return description
    308305
    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.tag
    333306
    334307    def render_services(self, ctx, data):
  • TabularUnified src/allmydata/web/static/css/new-tahoe.css

    r8df1ed1b rc385e95  
    5454}
    5555
    56 .magic-folder-status {
    57     clear: left;
    58     margin-left: 40px;  /* width of status-indicator + margins */
    59 }
    60 
    6156.furl {
    6257    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">
    22  <head>
    33    <title>Tahoe-LAFS - Operational Statistics</title>
     
    66    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    77  </head>
    8   <body n:data="get_stats">
     8  <body>
    99
    1010<h1>Operational Statistics</h1>
     
    1212<h2>General</h2>
    1313
    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>
    3422
    3523<h2>Raw Stats:</h2>
    36 <pre n:render="raw" />
     24<pre t:render="raw" />
    3725
    3826<div>Return to the <a href="/">Welcome Page</a></div>
  • TabularUnified src/allmydata/web/status.py

    r8df1ed1b rc385e95  
    33import json
    44from twisted.internet import defer
     5from twisted.python.filepath import FilePath
    56from twisted.web.resource import Resource
     7from twisted.web.template import (
     8    Element,
     9    XMLFile,
     10    renderer,
     11    renderElement,
     12)
    613from nevow import rend, tags as T
    714from allmydata.util import base32, idlib
     
    1522    render_time,
    1623    MultiFormatPage,
     24    MultiFormatResource,
    1725)
    1826from allmydata.interfaces import IUploadStatus, IDownloadStatus, \
     
    11651173        return str(data["chk_upload_helper.encoded_bytes"])
    11661174
    1167 
    1168 class Statistics(MultiFormatPage):
    1169     docFactory = getxmlfile("statistics.xhtml")
     1175# Render "/statistics" page.
     1176class Statistics(MultiFormatResource):
     1177    """Class that renders "/statistics" page.
     1178
     1179    :param _allmydata.stats.StatsProvider provider: node statistics
     1180           provider.
     1181    """
    11701182
    11711183    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))
    11741189
    11751190    def render_JSON(self, req):
    1176         stats = self.provider.get_stats()
     1191        stats = self._provider.get_stats()
    11771192        req.setHeader("content-type", "text/plain")
    11781193        return json.dumps(stats, indent=1) + "\n"
    11791194
    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]
     1195class 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  
    160160          </div>
    161161
    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 
    169162          <div class="row-fluid">
    170163            <h2>
Note: See TracChangeset for help on using the changeset viewer.