Beispiel #1
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()
Beispiel #2
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()
Beispiel #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"))
Beispiel #4
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([
                (ClientEvent.MOUNTPOINT_STARTING, expected),
                (ClientEvent.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([(ClientEvent.MOUNTPOINT_STOPPED,
                                            expected)])

        if not manual_unmount:
            # Mountpoint should be stopped by now
            spy.assert_events_occured([(ClientEvent.MOUNTPOINT_STOPPED,
                                        expected)])
Beispiel #5
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", )
Beispiel #6
0
async def logged_client_factory(config: ClientConfig,
                                device: LocalDevice,
                                event_bus: Optional[EventBus] = None):
    event_bus = event_bus or EventBus()

    # Get the pattern filter
    if config.pattern_filter is None:
        pattern_filter = get_pattern_filter(config.pattern_filter_path)
    else:
        try:
            pattern_filter = re.compile(config.pattern_filter)
        except re.error:
            pattern_filter = get_pattern_filter(config.pattern_filter_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,
    )

    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,
                          pattern_filter) 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 LoggedClient(
                    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,
                )
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 = 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)
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 = 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)
Beispiel #9
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()
Beispiel #10
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()
Beispiel #11
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()
Beispiel #12
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("guardata.client.mountpoint.manager.import_function",
                        _import)
    with pytest.raises(
        (MountpointFuseNotAvailable, MountpointWinfspNotAvailable)):
        async with mountpoint_manager_factory(alice_user_fs, event_bus,
                                              base_mountpoint):
            pass
Beispiel #13
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()
Beispiel #14
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("/"))

        # 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()
Beispiel #15
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)
        await workspace.sync()

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

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