Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 39 additions & 4 deletions src/base/master/swarm_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -1606,15 +1606,50 @@ def _readonly_mount_arg(source: str, target: str) -> str:
return f"type={mount_type},source={source},destination={target},readonly"


#: tmpfs options Docker Swarm already applies by default, so honouring them
#: needs no translation. ``noexec`` belongs here because tmpfs is noexec unless
#: told otherwise -- which is exactly why ``exec`` below cannot be dropped.
_SWARM_TMPFS_DEFAULT_OPTIONS = frozenset({"rw", "noexec", "nosuid", "nodev"})


def _tmpfs_mount_arg(value: str) -> str:
"""Translate a ``docker run`` tmpfs spec into a Swarm ``--mount`` argument.

Swarm's ``--mount type=tmpfs`` expresses only ``tmpfs-size`` and
``tmpfs-mode`` (verified against Docker 29.2.1: a bare ``exec`` is rejected
as a non key=value field, and ``tmpfs-options`` is an unknown option). Any
option that cannot be translated is refused rather than dropped: a silently
discarded ``exec`` yields a noexec ``/tmp``, so anything installed there
loads no shared object and the failure surfaces far from its cause.
"""

path, separator, options = value.partition(":")
if not path.startswith("/"):
raise DockerOrchestrationError(f"tmpfs mount must be absolute: {path}")
arg = f"type=tmpfs,destination={path}"
if separator:
for option in options.split(","):
if option.startswith("size="):
arg += f",tmpfs-size={option.removeprefix('size=')}"
if not separator:
return arg
for raw_option in options.split(","):
option = raw_option.strip()
if not option or option in _SWARM_TMPFS_DEFAULT_OPTIONS:
continue
if option.startswith("size="):
arg += f",tmpfs-size={option.removeprefix('size=')}"
elif option.startswith("mode="):
arg += f",tmpfs-mode={option.removeprefix('mode=')}"
elif option == "exec":
raise DockerOrchestrationError(
f"tmpfs mount {path} requests exec, which Docker Swarm cannot "
"express: --mount type=tmpfs supports only tmpfs-size and "
"tmpfs-mode. Dropping the flag would mount the path noexec and "
"break loading anything installed there. Run this workload on "
"the broker backend instead."
)
else:
raise DockerOrchestrationError(
f"tmpfs mount {path} carries option {option!r} that Docker "
"Swarm cannot express; refusing rather than dropping it."
)
return arg


Expand Down
46 changes: 46 additions & 0 deletions tests/unit/test_swarm_tmpfs_exec.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"""Swarm tmpfs translation must never silently drop execution semantics.

The own_runner job container installs the miner agent into ``/tmp/.local`` under
a read-only rootfs, so its tmpfs spec carries ``exec``. Docker Swarm's
``--mount type=tmpfs`` cannot express that flag (verified against Docker
29.2.1: ``exec`` is rejected as a non key=value field and ``tmpfs-options`` is
an unknown option), and tmpfs defaults to ``noexec``. Dropping the flag on the
way through therefore produces a mount that loads no shared object -- the exact
production failure that cost hours to diagnose. Refuse instead.
"""

from __future__ import annotations

import pytest

from base.master.docker_orchestrator import DockerOrchestrationError
from base.master.swarm_backend import _tmpfs_mount_arg


def test_exec_is_refused_rather_than_silently_dropped() -> None:
with pytest.raises(DockerOrchestrationError) as excinfo:
_tmpfs_mount_arg("/tmp:rw,nosuid,exec,size=2g")
message = str(excinfo.value)
assert "exec" in message
assert "/tmp" in message


def test_default_noexec_spec_still_translates() -> None:
arg = _tmpfs_mount_arg("/tmp:rw,noexec,nosuid,size=512m")
assert "type=tmpfs" in arg
assert "destination=/tmp" in arg
assert "tmpfs-size=512m" in arg


def test_spec_without_size_translates() -> None:
assert _tmpfs_mount_arg("/tmp:rw,nosuid,nodev") == "type=tmpfs,destination=/tmp"


def test_unknown_option_is_refused_rather_than_dropped() -> None:
with pytest.raises(DockerOrchestrationError):
_tmpfs_mount_arg("/tmp:rw,totally-unknown-option")


def test_relative_path_still_refused() -> None:
with pytest.raises(DockerOrchestrationError):
_tmpfs_mount_arg("tmp:size=1m")
Loading