Ejemplo n.º 1
0
async def test_mountpoint_access_unicode(base_mountpoint, alice_user_fs,
                                         event_bus):
    weird_name = "ÉŸ奇怪😀🔫🐍"

    wid = await alice_user_fs.workspace_create(weird_name)
    workspace = alice_user_fs.get_workspace(wid)
    await workspace.touch(f"/{weird_name}")

    # Now we can start fuse
    async with mountpoint_manager_factory(
        alice_user_fs, event_bus, base_mountpoint) as mountpoint_manager:

        await mountpoint_manager.mount_workspace(wid)

        root_path = mountpoint_manager.get_path_in_mountpoint(
            wid, FsPath(f"/"))

        # Work around trio issue #1308 (https://github.com/python-trio/trio/issues/1308)
        items = await trio.to_thread.run_sync(
            lambda: [path.name for path in Path(root_path).iterdir()])
        assert items == [weird_name]

        item_path = mountpoint_manager.get_path_in_mountpoint(
            wid, FsPath(f"/{weird_name}"))
        assert await trio.Path(item_path).exists()
Ejemplo n.º 2
0
async def test_cancel_mount_workspace(base_mountpoint, alice_user_fs,
                                      event_bus, timeout):
    """
    This function tests the race conditions between the mounting of a workspace
    and trio cancellation. In particular, it produces interesting results when trying to
    unmount a workspace while it's still initializing.

    The following timeout values are useful for more thorough testing:

        [x * 0.00001 for x in range(2000, 2500)]
    """
    wid = await alice_user_fs.workspace_create("w")

    # The timeout for `_stop_fuse_thread` is 1 second (3 seconds for macOS),
    # so let's use a slightly lower timeout to make sure a potential failure
    # doesn't go undetected.
    timeout = 3.0 if sys.platform == "darwin" else 1.0
    with trio.fail_after(timeout * 0.9):

        async with mountpoint_manager_factory(
                alice_user_fs, event_bus,
                base_mountpoint) as mountpoint_manager:

            with trio.move_on_after(timeout) as cancel_scope:
                await mountpoint_manager.mount_workspace(wid)
            if cancel_scope.cancelled_caught:
                with pytest.raises(MountpointNotMounted):
                    mountpoint_manager.get_path_in_mountpoint(wid, FsPath("/"))
            else:
                path = trio.Path(
                    mountpoint_manager.get_path_in_mountpoint(
                        wid, FsPath("/")))
                await path.exists()
                assert not await (path / "foo").exists()
Ejemplo n.º 3
0
async def test_get_path_in_mountpoint(base_mountpoint, alice_user_fs,
                                      event_bus):
    # Populate a bit the fs first...
    wid = await alice_user_fs.workspace_create("mounted_wksp")
    wid2 = await alice_user_fs.workspace_create("not_mounted_wksp")
    workspace1 = alice_user_fs.get_workspace(wid)
    workspace2 = alice_user_fs.get_workspace(wid2)
    await workspace1.touch("/bar.txt")
    await workspace2.touch("/foo.txt")

    # Now we can start fuse
    async with mountpoint_manager_factory(
        alice_user_fs, event_bus, base_mountpoint) as mountpoint_manager:
        await mountpoint_manager.mount_workspace(wid)

        bar_path = mountpoint_manager.get_path_in_mountpoint(
            wid, FsPath("/bar.txt"))

        assert isinstance(bar_path, PurePath)
        # Windows uses drives, not base_mountpoint
        if os.name != "nt":
            expected = base_mountpoint / "mounted_wksp" / "bar.txt"
            assert str(bar_path) == str(expected.absolute())
        assert await trio.Path(bar_path).exists()

        with pytest.raises(MountpointNotMounted):
            mountpoint_manager.get_path_in_mountpoint(wid2, FsPath("/foo.txt"))
Ejemplo n.º 4
0
 async def _mount_alice2_w_mountpoint(*, task_status=trio.TASK_STATUS_IGNORED):
     async with mountpoint_manager_factory(
         alice2_user_fs, alice2_user_fs.event_bus, base_mountpoint
     ) as alice2_mountpoint_manager:
         await alice2_mountpoint_manager.mount_workspace(wid)
         task_status.started()
         await trio.sleep_forever()
Ejemplo n.º 5
0
async def test_idempotent_mount(base_mountpoint, alice_user_fs, event_bus,
                                manual_unmount):
    # Populate a bit the fs first...

    wid = await alice_user_fs.workspace_create("w")
    workspace = alice_user_fs.get_workspace(wid)
    await workspace.touch("/bar.txt")

    # Now we can start fuse
    async with mountpoint_manager_factory(
        alice_user_fs, event_bus, base_mountpoint) as mountpoint_manager:

        await mountpoint_manager.mount_workspace(wid)
        bar_txt = get_path_in_mountpoint(mountpoint_manager, wid, "/bar.txt")

        assert await bar_txt.exists()

        with pytest.raises(MountpointAlreadyMounted):
            await mountpoint_manager.mount_workspace(wid)
        assert await bar_txt.exists()

        await mountpoint_manager.unmount_workspace(wid)
        assert not await bar_txt.exists()

        with pytest.raises(MountpointNotMounted):
            await mountpoint_manager.unmount_workspace(wid)
        assert not await bar_txt.exists()

        await mountpoint_manager.mount_workspace(wid)
        assert await bar_txt.exists()
Ejemplo n.º 6
0
async def test_mountpoint_path_already_in_use_concurrent_with_non_empty_dir(
        monkeypatch, base_mountpoint, alice_user_fs):
    wid = await alice_user_fs.workspace_create("w")
    mountpoint_path = base_mountpoint.absolute() / "w"

    # Here instead of checking the path can be used as a mountpoint, we
    # actually make it unsuitable to check the following behavior
    async def _mocked_bootstrap_mountpoint(*args):
        trio_mountpoint_path = trio.Path(f"{mountpoint_path}")
        await trio_mountpoint_path.mkdir(parents=True)
        file_path = trio_mountpoint_path / "bar.txt"
        await file_path.touch()
        st_dev = (await trio_mountpoint_path.stat()).st_dev
        return mountpoint_path, st_dev

    monkeypatch.setattr(
        "parsec.core.mountpoint.fuse_runner._bootstrap_mountpoint",
        _mocked_bootstrap_mountpoint)

    # Now we can start fuse
    async with mountpoint_manager_factory(
        alice_user_fs, alice_user_fs.event_bus,
        base_mountpoint) as alice_mountpoint_manager:
        with pytest.raises(MountpointDriverCrash) as exc:
            await alice_mountpoint_manager.mount_workspace(wid)
        assert exc.value.args == (
            f"Fuse has crashed on {mountpoint_path}: EPERM", )
Ejemplo n.º 7
0
async def test_unmount_with_fusermount(base_mountpoint, alice, alice_user_fs,
                                       event_bus):
    wid = await alice_user_fs.workspace_create("w")
    workspace = alice_user_fs.get_workspace(wid)
    await workspace.touch("/bar.txt")

    async with mountpoint_manager_factory(
            alice_user_fs, event_bus, base_mountpoint) as mountpoint_manager:

        with event_bus.listen() as spy:
            mountpoint_path = await mountpoint_manager.mount_workspace(wid)
            command = f"fusermount -u {mountpoint_path}".split()
            expected = {
                "mountpoint": mountpoint_path,
                "workspace_id": wid,
                "timestamp": None
            }

            completed_process = await trio.run_process(command)
            with trio.fail_after(1):
                # fusermount might fail for some reasons
                while completed_process.returncode:
                    completed_process = await trio.run_process(command)
                await spy.wait(CoreEvent.MOUNTPOINT_STOPPED, expected)

        assert not await trio.Path(mountpoint_path / "bar.txt").exists()

    # Mountpoint path should be removed on umounting
    assert not await trio.Path(mountpoint_path).exists()
Ejemplo n.º 8
0
async def test_mountpoint_iterdir_with_many_files(n, base_path,
                                                  base_mountpoint,
                                                  alice_user_fs, event_bus):
    wid = await alice_user_fs.workspace_create("w")
    workspace = alice_user_fs.get_workspace(wid)
    await workspace.mkdir(base_path, parents=True, exist_ok=True)
    names = [f"some_file_{i:03d}.txt" for i in range(n)]
    path_list = [FsPath(f"{base_path}/{name}") for name in names]

    for path in path_list:
        await workspace.touch(path)

    # Now we can start fuse
    async with mountpoint_manager_factory(
            alice_user_fs, event_bus, base_mountpoint) as mountpoint_manager:

        await mountpoint_manager.mount_workspace(wid)

        test_path = mountpoint_manager.get_path_in_mountpoint(
            wid, FsPath(base_path))

        # Work around trio issue #1308 (https://github.com/python-trio/trio/issues/1308)
        items = await trio.to_thread.run_sync(
            lambda: [path.name for path in Path(test_path).iterdir()])
        assert items == names

        for path in path_list:
            item_path = mountpoint_manager.get_path_in_mountpoint(wid, path)
            assert await trio.Path(item_path).exists()
Ejemplo n.º 9
0
async def test_mount_and_explore_workspace(base_mountpoint, alice_user_fs,
                                           event_bus, manual_unmount):
    # Populate a bit the fs first...

    wid = await alice_user_fs.workspace_create("w")
    workspace = alice_user_fs.get_workspace(wid)
    await workspace.mkdir("/foo")
    await workspace.touch("/bar.txt")
    await workspace.write_bytes("/bar.txt", b"Hello world !")

    # Now we can start fuse

    with event_bus.listen() as spy:

        async with mountpoint_manager_factory(
                alice_user_fs, event_bus,
                base_mountpoint) as mountpoint_manager:

            await mountpoint_manager.mount_workspace(wid)
            mountpoint_path = get_path_in_mountpoint(mountpoint_manager, wid,
                                                     "/")
            expected = {
                "mountpoint": mountpoint_path,
                "workspace_id": wid,
                "timestamp": None
            }

            spy.assert_events_occured([
                (CoreEvent.MOUNTPOINT_STARTING, expected),
                (CoreEvent.MOUNTPOINT_STARTED, expected),
            ])

            # Finally explore the mountpoint

            def inspect_mountpoint():
                wksp_children = set(os.listdir(mountpoint_path))
                assert wksp_children == {"foo", "bar.txt"}

                bar_stat = os.stat(f"{mountpoint_path}/bar.txt")
                assert bar_stat.st_size == len(b"Hello world !")

                with open(f"{mountpoint_path}/bar.txt", "rb") as fd:
                    bar_txt = fd.read()
                assert bar_txt == b"Hello world !"

            # Note given python fs api is blocking, we must run it inside a thread
            # to avoid blocking the trio loop and ending up in a deadlock
            await trio.to_thread.run_sync(inspect_mountpoint)

            if manual_unmount:
                await mountpoint_manager.unmount_workspace(wid)
                # Mountpoint should be stopped by now
                spy.assert_events_occured([(CoreEvent.MOUNTPOINT_STOPPED,
                                            expected)])

        if not manual_unmount:
            # Mountpoint should be stopped by now
            spy.assert_events_occured([(CoreEvent.MOUNTPOINT_STOPPED, expected)
                                       ])
Ejemplo n.º 10
0
async def test_mount_unknown_workspace(base_mountpoint, alice_user_fs,
                                       event_bus):
    async with mountpoint_manager_factory(
            alice_user_fs, event_bus, base_mountpoint) as mountpoint_manager:
        wid = uuid4()
        with pytest.raises(MountpointConfigurationError) as exc:
            await mountpoint_manager.mount_workspace(wid)

        assert exc.value.args == (f"Workspace `{wid}` doesn't exist", )
Ejemplo n.º 11
0
async def logged_core_factory(config: CoreConfig,
                              device: LocalDevice,
                              event_bus: Optional[EventBus] = None):
    event_bus = event_bus or EventBus()
    prevent_sync_pattern = get_prevent_sync_pattern(
        config.prevent_sync_pattern_path)
    backend_conn = BackendAuthenticatedConn(
        addr=device.organization_addr,
        device_id=device.device_id,
        signing_key=device.signing_key,
        event_bus=event_bus,
        max_cooldown=config.backend_max_cooldown,
        max_pool=config.backend_max_connections,
        keepalive=config.backend_connection_keepalive,
    )

    remote_devices_manager = RemoteDevicesManager(backend_conn.cmds,
                                                  device.root_verify_key)
    async with UserFS.run(
            data_base_dir=config.data_base_dir,
            device=device,
            backend_cmds=backend_conn.cmds,
            remote_devices_manager=remote_devices_manager,
            event_bus=event_bus,
            prevent_sync_pattern=prevent_sync_pattern,
            preferred_language=config.gui_language,
            workspace_storage_cache_size=config.workspace_storage_cache_size,
    ) as user_fs:

        backend_conn.register_monitor(
            partial(monitor_messages, user_fs, event_bus))
        backend_conn.register_monitor(partial(monitor_sync, user_fs,
                                              event_bus))

        async with backend_conn.run():
            async with mountpoint_manager_factory(
                    user_fs,
                    event_bus,
                    config.mountpoint_base_dir,
                    mount_all=config.mountpoint_enabled,
                    mount_on_workspace_created=config.mountpoint_enabled,
                    mount_on_workspace_shared=config.mountpoint_enabled,
                    unmount_on_workspace_revoked=config.mountpoint_enabled,
                    exclude_from_mount_all=config.disabled_workspaces,
            ) as mountpoint_manager:

                yield LoggedCore(
                    config=config,
                    device=device,
                    event_bus=event_bus,
                    mountpoint_manager=mountpoint_manager,
                    user_fs=user_fs,
                    remote_devices_manager=remote_devices_manager,
                    backend_conn=backend_conn,
                )
Ejemplo n.º 12
0
async def test_inconsistent_folder_with_network(base_mountpoint,
                                                running_backend,
                                                alice_user_fs):
    async with mountpoint_manager_factory(
            alice_user_fs, alice_user_fs.event_bus,
            base_mountpoint) as alice_mountpoint_manager:
        workspace = await create_inconsistent_workspace(alice_user_fs)
        mountpoint_path = Path(await alice_mountpoint_manager.mount_workspace(
            workspace.workspace_id))
        await trio.to_thread.run_sync(_os_tests, mountpoint_path, errno.EACCES,
                                      WINDOWS_ERROR_PERMISSION_DENIED)
Ejemplo n.º 13
0
async def test_inconsistent_folder_no_network(base_mountpoint, running_backend,
                                              alice_user_fs):
    async with mountpoint_manager_factory(
            alice_user_fs, alice_user_fs.event_bus,
            base_mountpoint) as alice_mountpoint_manager:
        workspace = await create_inconsistent_workspace(alice_user_fs)
        mountpoint_path = Path(await alice_mountpoint_manager.mount_workspace(
            workspace.workspace_id))
        with running_backend.offline():
            await trio.to_thread.run_sync(_os_tests, mountpoint_path,
                                          errno.EHOSTUNREACH,
                                          WINDOWS_ERROR_HOST_UNREACHABLE)
Ejemplo n.º 14
0
async def test_unmount_due_to_cancelled_scope(base_mountpoint, alice, alice_user_fs, event_bus):
    mountpoint_path = base_mountpoint / "w"
    wid = await alice_user_fs.workspace_create(EntryName("w"))

    with trio.CancelScope() as cancel_scope:
        async with mountpoint_manager_factory(
            alice_user_fs, event_bus, base_mountpoint
        ) as mountpoint_manager:

            await mountpoint_manager.mount_workspace(wid)
            cancel_scope.cancel()

    # Mountpoint path should be removed on umounting
    assert not await trio.Path(mountpoint_path).exists()
Ejemplo n.º 15
0
async def test_mountpoint_path_already_in_use(base_mountpoint, running_backend,
                                              alice_user_fs, alice2_user_fs):
    # Create a workspace and make it available in two devices
    wid = await alice_user_fs.workspace_create("w")
    await alice_user_fs.sync()
    await alice2_user_fs.sync()

    # Easily differenciate alice&alice2
    await alice2_user_fs.get_workspace(wid).touch("/I_am_alice2.txt")
    await alice_user_fs.get_workspace(wid).touch("/I_am_alice.txt")

    naive_workspace_path = (base_mountpoint / "w").absolute()

    # Default workspace path already exists, souldn't be able to use it
    await trio.Path(base_mountpoint / "w").mkdir(parents=True)
    await trio.Path(base_mountpoint / "w" / "bar.txt").touch()

    async with mountpoint_manager_factory(
            alice_user_fs, alice_user_fs.event_bus, base_mountpoint
    ) as alice_mountpoint_manager, mountpoint_manager_factory(
            alice2_user_fs, alice2_user_fs.event_bus,
            base_mountpoint) as alice2_mountpoint_manager:
        # Alice mount the workspace first
        alice_mountpoint_path = await alice_mountpoint_manager.mount_workspace(
            wid)
        assert str(alice_mountpoint_path) == f"{naive_workspace_path} (2)"

        # Alice2 should also be able to mount the workspace without name clashing
        alice2_mountpoint_path = await alice2_mountpoint_manager.mount_workspace(
            wid)
        assert str(alice2_mountpoint_path) == f"{naive_workspace_path} (3)"

        # Finally make sure each workspace is well mounted
        assert await trio.Path(alice_mountpoint_path / "I_am_alice.txt"
                               ).exists()
        assert await trio.Path(alice2_mountpoint_path / "I_am_alice2.txt"
                               ).exists()
Ejemplo n.º 16
0
    async def _run_mountpoint(
        base_mountpoint, bootstrap_cb, *, task_status=trio.TASK_STATUS_IGNORED
    ):
        device = local_device_factory()
        async with user_fs_factory(device) as user_fs:

            async with mountpoint_manager_factory(
                user_fs, user_fs.event_bus, base_mountpoint, debug=False
            ) as mountpoint_manager:

                if bootstrap_cb:
                    await bootstrap_cb(user_fs, mountpoint_manager)

                task_status.started((user_fs, mountpoint_manager))
                await trio.sleep_forever()
Ejemplo n.º 17
0
async def test_base_mountpoint_not_created(base_mountpoint, alice_user_fs,
                                           event_bus):
    # Path should be created if it doesn' exist
    base_mountpoint = base_mountpoint / "dummy/dummy/dummy"

    wid = await alice_user_fs.workspace_create("w")
    workspace = alice_user_fs.get_workspace(wid)
    await workspace.touch("/bar.txt")

    # Now we can start fuse
    async with mountpoint_manager_factory(
        alice_user_fs, event_bus, base_mountpoint) as mountpoint_manager:
        await mountpoint_manager.mount_workspace(wid)
        bar_txt = get_path_in_mountpoint(mountpoint_manager, wid, "/bar.txt")
        assert await bar_txt.exists()
Ejemplo n.º 18
0
async def test_runner_not_available(monkeypatch, alice_user_fs, event_bus):
    base_mountpoint = Path("/foo")

    def _import(name):
        if name == "winfspy":
            raise RuntimeError()
        else:
            raise ImportError()

    monkeypatch.setattr("parsec.core.mountpoint.manager.import_function",
                        _import)
    with pytest.raises(
        (MountpointFuseNotAvailable, MountpointWinfspNotAvailable)):
        async with mountpoint_manager_factory(alice_user_fs, event_bus,
                                              base_mountpoint):
            pass
Ejemplo n.º 19
0
async def test_cancel_mount_workspace(base_mountpoint, alice_user_fs,
                                      event_bus, timeout):

    wid = await alice_user_fs.workspace_create("w")

    async with mountpoint_manager_factory(
            alice_user_fs, event_bus, base_mountpoint) as mountpoint_manager:

        with trio.move_on_after(timeout) as cancel_scope:
            await mountpoint_manager.mount_workspace(wid)
        if cancel_scope.cancelled_caught:
            with pytest.raises(MountpointNotMounted):
                mountpoint_manager.get_path_in_mountpoint(wid, FsPath("/"))
        else:
            path = trio.Path(
                mountpoint_manager.get_path_in_mountpoint(wid, FsPath("/")))
            await path.exists()
            assert not await (path / "foo").exists()
Ejemplo n.º 20
0
async def test_mountpoint_path_already_in_use_concurrent_with_mountpoint(
        monkeypatch, base_mountpoint, running_backend, alice_user_fs,
        alice2_user_fs):
    # Create a workspace and make it available in two devices
    wid = await alice_user_fs.workspace_create("w")
    await alice_user_fs.sync()
    await alice2_user_fs.sync()

    mountpoint_path = base_mountpoint.absolute() / "w"

    async def _mount_alice2_w_mountpoint(*,
                                         task_status=trio.TASK_STATUS_IGNORED):
        async with mountpoint_manager_factory(
                alice2_user_fs, alice2_user_fs.event_bus,
                base_mountpoint) as alice2_mountpoint_manager:
            await alice2_mountpoint_manager.mount_workspace(wid)
            task_status.started()
            await trio.sleep_forever()

    async with trio.open_service_nursery() as nursery:
        await nursery.start(_mount_alice2_w_mountpoint)

        # Here instead of checking the path can be used as a mountpoint, we
        # actually lead it into error
        async def _mocked_bootstrap_mountpoint(*args):
            trio_mountpoint_path = trio.Path(f"{mountpoint_path}")
            st_dev = (await trio_mountpoint_path.stat()).st_dev
            return mountpoint_path, st_dev

        monkeypatch.setattr(
            "parsec.core.mountpoint.fuse_runner._bootstrap_mountpoint",
            _mocked_bootstrap_mountpoint)

        # Now we can start fuse
        async with mountpoint_manager_factory(
            alice_user_fs, alice_user_fs.event_bus,
            base_mountpoint) as alice_mountpoint_manager:
            with pytest.raises(MountpointDriverCrash) as exc:
                await alice_mountpoint_manager.mount_workspace(wid)
            assert exc.value.args == (
                f"Fuse has crashed on {mountpoint_path}: EPERM", )

        # Test is over, stop alice2 mountpoint and exit
        nursery.cancel_scope.cancel()
Ejemplo n.º 21
0
async def logged_core_factory(
    config: CoreConfig, device: LocalDevice, event_bus: Optional[EventBus] = None
):
    event_bus = event_bus or EventBus()

    backend_conn = BackendAuthenticatedConn(
        addr=device.organization_addr,
        device_id=device.device_id,
        signing_key=device.signing_key,
        event_bus=event_bus,
        max_cooldown=config.backend_max_cooldown,
        max_pool=config.backend_max_connections,
        keepalive=config.backend_connection_keepalive,
    )

    path = config.data_base_dir / device.slug
    remote_devices_manager = RemoteDevicesManager(backend_conn.cmds, device.root_verify_key)
    async with UserFS.run(
        device, path, backend_conn.cmds, remote_devices_manager, event_bus
    ) as user_fs:

        backend_conn.register_monitor(partial(monitor_messages, user_fs, event_bus))
        backend_conn.register_monitor(partial(monitor_sync, user_fs, event_bus))

        async with backend_conn.run():

            async with mountpoint_manager_factory(
                user_fs, event_bus, config.mountpoint_base_dir
            ) as mountpoint_manager:

                yield LoggedCore(
                    config=config,
                    device=device,
                    event_bus=event_bus,
                    remote_devices_manager=remote_devices_manager,
                    mountpoint_manager=mountpoint_manager,
                    backend_conn=backend_conn,
                    user_fs=user_fs,
                )
Ejemplo n.º 22
0
async def test_cancel_mount_workspace(base_mountpoint, alice_user_fs,
                                      event_bus):
    """
    This function tests the race conditions between the mounting of a workspace
    and trio cancellation. In particular, it produces interesting results when trying to
    unmount a workspace while it's still initializing.

    The following timeout values are useful for more thorough testing:

        [x * 0.00001 for x in range(2000, 2500)]
    """
    wid = await alice_user_fs.workspace_create(EntryName("w"))

    # Reuse the same mountpoint manager for all the mountings to
    # make sure state is not polutated by previous mount attempts
    async with mountpoint_manager_factory(
        alice_user_fs, event_bus, base_mountpoint) as mountpoint_manager:

        for timeout in count(0, 0.002):
            print(f"timeout: {timeout}")

            async with real_clock_timeout():

                with trio.move_on_after(timeout) as cancel_scope:
                    await mountpoint_manager.mount_workspace(wid)

                if cancel_scope.cancelled_caught:
                    with pytest.raises(MountpointNotMounted):
                        mountpoint_manager.get_path_in_mountpoint(
                            wid, FsPath("/"))
                else:
                    # Sanity check
                    path = trio.Path(
                        mountpoint_manager.get_path_in_mountpoint(
                            wid, FsPath("/")))
                    await path.exists()
                    # Timeout has become too high to be useful, time to stop the test
                    break
Ejemplo n.º 23
0
async def test_hard_crash_in_fuse_thread(base_mountpoint, alice_user_fs):
    wid = await alice_user_fs.workspace_create("w")
    mountpoint_path = base_mountpoint / "w"

    class ToughLuckError(Exception):
        pass

    def _crash_fuse(*args, **kwargs):
        raise ToughLuckError("Tough luck...")

    with patch("parsec.core.mountpoint.fuse_runner.FUSE", new=_crash_fuse):
        async with mountpoint_manager_factory(
                alice_user_fs, alice_user_fs.event_bus,
                base_mountpoint) as mountpoint_manager:

            with pytest.raises(MountpointDriverCrash) as exc:
                await mountpoint_manager.mount_workspace(wid)
            assert exc.value.args == (
                f"Fuse has crashed on {mountpoint_path}: Unknown error code: Tough luck...",
            )

    # Mountpoint path should be removed on umounting
    assert not await trio.Path(mountpoint_path).exists()
Ejemplo n.º 24
0
async def logged_core_factory(
    config: CoreConfig,
    device: LocalDevice,
    event_bus: Optional[EventBus] = None,
    mountpoint: Optional[Path] = None,
):
    if config.mountpoint_enabled and os.name == "nt":
        logger.warning("Mountpoint disabled (not supported yet on Windows)")
        config = config.evolve(mountpoint_enabled=False)

    event_bus = event_bus or EventBus()

    # Plenty of nested scope to order components init/teardown
    async with trio.open_nursery() as root_nursery:
        # TODO: Currently backend_listen_events connect to backend and
        # switch to listen events mode, then monitors kick in and send it
        # events about which beacons to listen on, obliging to restart the
        # listen connection...
        backend_online = await root_nursery.start(backend_listen_events,
                                                  device, event_bus)

        async with backend_cmds_factory(
                device.organization_addr,
                device.device_id,
                device.signing_key,
                config.backend_max_connections,
        ) as backend_cmds_pool:

            local_db = LocalDB(config.data_base_dir / device.device_id)

            encryption_manager = EncryptionManager(device, local_db,
                                                   backend_cmds_pool)
            fs = FS(device, local_db, backend_cmds_pool, encryption_manager,
                    event_bus)

            async with trio.open_nursery() as monitor_nursery:
                # Finally start monitors

                # Monitor connection must be first given it will watch on
                # other monitors' events
                await monitor_nursery.start(monitor_backend_connection,
                                            backend_online, event_bus)
                await monitor_nursery.start(monitor_beacons, device, fs,
                                            event_bus)
                await monitor_nursery.start(monitor_messages, backend_online,
                                            fs, event_bus)
                await monitor_nursery.start(monitor_sync, backend_online, fs,
                                            event_bus)

                # TODO: rework mountpoint manager to avoid init/teardown
                mountpoint_manager = mountpoint_manager_factory(fs, event_bus)
                await mountpoint_manager.init(monitor_nursery)
                if config.mountpoint_enabled:
                    if not mountpoint:
                        mountpoint = config.mountpoint_base_dir / device.device_id
                    await mountpoint_manager.start(mountpoint)

                try:
                    yield LoggedCore(
                        config=config,
                        device=device,
                        local_db=local_db,
                        event_bus=event_bus,
                        encryption_manager=encryption_manager,
                        mountpoint_manager=mountpoint_manager,
                        backend_cmds=backend_cmds_pool,
                        fs=fs,
                    )
                    root_nursery.cancel_scope.cancel()

                finally:
                    if config.mountpoint_enabled:
                        await mountpoint_manager.teardown()
Ejemplo n.º 25
0
async def test_mountpoint_revoke_access(
    base_mountpoint,
    alice_user_fs,
    alice2_user_fs,
    bob_user_fs,
    event_bus,
    running_backend,
    revoking,
):
    # Parametrization
    new_role = None if revoking == "read" else WorkspaceRole.READER

    # Bob creates and share two files with Alice
    wid = await create_shared_workspace("w", bob_user_fs, alice_user_fs,
                                        alice2_user_fs)
    workspace = bob_user_fs.get_workspace(wid)
    await workspace.touch("/foo.txt")
    await workspace.touch("/bar.txt")
    await workspace.touch("/to_delete.txt")
    await workspace.sync()

    def get_root_path(mountpoint_manager):
        root_path = mountpoint_manager.get_path_in_mountpoint(wid, FsPath("/"))
        # A trio path is required here, otherwise we risk a messy deadlock!
        return trio.Path(root_path)

    async def assert_cannot_read(mountpoint_manager, root_is_cached=False):
        root_path = get_root_path(mountpoint_manager)
        foo_path = root_path / "foo.txt"
        bar_path = root_path / "bar.txt"
        # For some reason, root_path.stat() does not trigger a new getattr call
        # to fuse operations if there has been a prior recent call to stat.
        if not root_is_cached:
            with pytest.raises(PermissionError):
                await root_path.stat()
        with pytest.raises(PermissionError):
            await foo_path.exists()
        with pytest.raises(PermissionError):
            await foo_path.read_bytes()
        with pytest.raises(PermissionError):
            await bar_path.exists()
        with pytest.raises(PermissionError):
            await bar_path.read_bytes()

    async def assert_cannot_write(mountpoint_manager, new_role):
        expected_error, expected_errno = PermissionError, errno.EACCES
        # On linux, errno.EROFS is not translated to a PermissionError
        if new_role is WorkspaceRole.READER and os.name != "nt":
            expected_error, expected_errno = OSError, errno.EROFS
        root_path = get_root_path(mountpoint_manager)
        foo_path = root_path / "foo.txt"
        bar_path = root_path / "bar.txt"
        with pytest.raises(expected_error) as ctx:
            await (root_path / "new_file.txt").touch()
        assert ctx.value.errno == expected_errno
        with pytest.raises(expected_error) as ctx:
            await (root_path / "new_directory").mkdir()
        assert ctx.value.errno == expected_errno
        with pytest.raises(expected_error) as ctx:
            await foo_path.write_bytes(b"foo contents")
        assert ctx.value.errno == expected_errno
        with pytest.raises(expected_error) as ctx:
            await foo_path.unlink()
        assert ctx.value.errno == expected_errno
        with pytest.raises(expected_error) as ctx:
            await bar_path.write_bytes(b"bar contents")
        assert ctx.value.errno == expected_errno
        with pytest.raises(expected_error) as ctx:
            await bar_path.unlink()

    async with mountpoint_manager_factory(
            alice_user_fs, event_bus, base_mountpoint) as mountpoint_manager:
        # Mount Bob workspace on Alice's side
        await mountpoint_manager.mount_workspace(wid)
        root_path = get_root_path(mountpoint_manager)

        # Alice can read
        await (root_path / "bar.txt").read_bytes()

        # Alice can write
        await (root_path / "bar.txt").write_bytes(b"test")

        # Alice can delete
        await (root_path / "to_delete.txt").unlink()
        assert not await (root_path / "to_delete.txt").exists()

        # Bob revokes Alice's read or write rights from her workspace
        await bob_user_fs.workspace_share(wid, alice_user_fs.device.user_id,
                                          new_role)

        # Let Alice process the info
        await alice_user_fs.process_last_messages()
        await alice2_user_fs.process_last_messages()

        # Alice still has read access
        if new_role is WorkspaceRole.READER:
            await (root_path / "bar.txt").read_bytes()

        # Alice no longer has read access
        else:
            await assert_cannot_read(mountpoint_manager, root_is_cached=True)

        # Alice no longer has write access
        await assert_cannot_write(mountpoint_manager, new_role)

    # Try again with Alice first device

    async with mountpoint_manager_factory(
            alice_user_fs, event_bus, base_mountpoint) as mountpoint_manager:
        # Mount alice workspace on bob's side once again
        await mountpoint_manager.mount_workspace(wid)
        root_path = get_root_path(mountpoint_manager)

        # Alice still has read access
        if new_role is WorkspaceRole.READER:
            await (root_path / "bar.txt").read_bytes()

        # Alice no longer has read access
        else:
            await assert_cannot_read(mountpoint_manager, root_is_cached=True)

        # Alice no longer has write access
        await assert_cannot_write(mountpoint_manager, new_role)

    # Try again with Alice second device

    async with mountpoint_manager_factory(
            alice2_user_fs, event_bus, base_mountpoint) as mountpoint_manager:
        # Mount alice workspace on bob's side once again
        await mountpoint_manager.mount_workspace(wid)
        root_path = get_root_path(mountpoint_manager)

        # Alice still has read access
        if new_role is WorkspaceRole.READER:
            await (root_path / "bar.txt").read_bytes()

        # Alice no longer has read access
        else:
            await assert_cannot_read(mountpoint_manager, root_is_cached=True)

        # Alice no longer has write access
        await assert_cannot_write(mountpoint_manager, new_role)