2026-08-28 00:19:45 +00:00
|
|
|
'''
|
|
|
|
|
Pre-runtime Linux network-namespace entry validation.
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
2026-08-28 03:31:03 +00:00
|
|
|
import errno
|
|
|
|
|
from functools import partial
|
|
|
|
|
import os
|
2026-08-28 00:19:45 +00:00
|
|
|
from pathlib import Path
|
|
|
|
|
from types import SimpleNamespace
|
2026-08-28 03:31:03 +00:00
|
|
|
from typing import (
|
|
|
|
|
Any,
|
|
|
|
|
BinaryIO,
|
|
|
|
|
)
|
2026-08-28 00:19:45 +00:00
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
|
|
2026-08-28 03:31:03 +00:00
|
|
|
from tractor import _child
|
|
|
|
|
from tractor.devx import _proctitle
|
|
|
|
|
from tractor.spawn import (
|
|
|
|
|
_entry,
|
|
|
|
|
_netns,
|
|
|
|
|
_spawn,
|
|
|
|
|
)
|
|
|
|
|
from tractor.trionics import patches
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _assert_fd_closed(namespace_fd: int) -> None:
|
|
|
|
|
'''
|
|
|
|
|
Assert that bootstrap consumed its child-owned descriptor.
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
with pytest.raises(OSError) as exc_info:
|
|
|
|
|
os.fstat(namespace_fd)
|
|
|
|
|
|
|
|
|
|
assert exc_info.value.errno == errno.EBADF
|
2026-08-28 00:19:45 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_enter_netns_rejects_mismatched_inherited_fd(
|
|
|
|
|
tmp_path: Path,
|
|
|
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
|
|
|
) -> None:
|
|
|
|
|
'''
|
|
|
|
|
A stale inherited FD must not enter a replacement namespace.
|
|
|
|
|
|
|
|
|
|
Open a real stand-in FD, declare a different expected inode and
|
|
|
|
|
replace `os.setns()` with a failure sentinel. The inode check must
|
|
|
|
|
reject the capability before any irreversible namespace entry.
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
token_path: Path = tmp_path / 'netns'
|
|
|
|
|
token_path.touch()
|
|
|
|
|
|
|
|
|
|
def fail_setns(namespace_fd: int, nstype: int) -> None:
|
|
|
|
|
raise AssertionError('`setns()` must not be called')
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr(_netns.os, 'setns', fail_setns)
|
|
|
|
|
namespace_file: BinaryIO
|
|
|
|
|
with token_path.open('rb') as namespace_file:
|
|
|
|
|
inode: int = token_path.stat().st_ino
|
|
|
|
|
with pytest.raises(
|
|
|
|
|
ValueError,
|
|
|
|
|
match=f'{inode}.*{inode + 1}',
|
|
|
|
|
):
|
|
|
|
|
_netns.enter_netns(
|
|
|
|
|
namespace_file.fileno(),
|
|
|
|
|
# Deliberately differ from `token_path`'s inode.
|
|
|
|
|
inode + 1,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_enter_netns_verifies_post_entry_inode(
|
|
|
|
|
tmp_path: Path,
|
|
|
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
|
|
|
) -> None:
|
|
|
|
|
'''
|
|
|
|
|
Successful `setns()` is insufficient without post-entry proof.
|
|
|
|
|
|
|
|
|
|
Use a real inherited FD and fake only the privileged syscall and
|
|
|
|
|
`/proc/self/ns/net` observation. The recorded calls prove both
|
|
|
|
|
hooks execute and `CLONE_NEWNET` constrains the namespace type;
|
|
|
|
|
the returned inode proves bootstrap observed the expected netns.
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
token_path: Path = tmp_path / 'netns'
|
|
|
|
|
token_path.touch()
|
|
|
|
|
setns_calls: list[tuple[int, int]] = []
|
|
|
|
|
stat_calls: list[Path] = []
|
|
|
|
|
|
|
|
|
|
def fake_setns(namespace_fd: int, nstype: int) -> None:
|
|
|
|
|
setns_calls.append((namespace_fd, nstype))
|
|
|
|
|
|
|
|
|
|
def fake_stat(path: Path) -> SimpleNamespace:
|
|
|
|
|
stat_calls.append(path)
|
|
|
|
|
return SimpleNamespace(st_ino=inode)
|
|
|
|
|
|
|
|
|
|
namespace_file: BinaryIO
|
|
|
|
|
with token_path.open('rb') as namespace_file:
|
|
|
|
|
namespace_fd: int = namespace_file.fileno()
|
|
|
|
|
inode: int = token_path.stat().st_ino
|
|
|
|
|
monkeypatch.setattr(_netns.os, 'setns', fake_setns)
|
|
|
|
|
monkeypatch.setattr(
|
|
|
|
|
type(_netns._SELF_NETNS),
|
|
|
|
|
'stat',
|
|
|
|
|
fake_stat,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
entered_inode: int = _netns.enter_netns(
|
|
|
|
|
namespace_fd,
|
|
|
|
|
inode,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert setns_calls == [
|
|
|
|
|
(namespace_fd, _netns.os.CLONE_NEWNET),
|
|
|
|
|
]
|
|
|
|
|
assert stat_calls == [_netns._SELF_NETNS]
|
|
|
|
|
assert entered_inode == inode
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_enter_netns_rejects_wrong_post_entry_namespace(
|
|
|
|
|
tmp_path: Path,
|
|
|
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
|
|
|
) -> None:
|
|
|
|
|
'''
|
|
|
|
|
Bootstrap must stop when the process lands in an unexpected netns.
|
|
|
|
|
|
|
|
|
|
Let the inherited FD check and fake syscall succeed, then report a
|
|
|
|
|
different `/proc/self/ns/net` inode. The post-entry guard must raise
|
|
|
|
|
instead of allowing actor runtime sockets to start in the wrong
|
|
|
|
|
namespace.
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
token_path: Path = tmp_path / 'netns'
|
|
|
|
|
token_path.touch()
|
|
|
|
|
|
|
|
|
|
def fake_setns(namespace_fd: int, nstype: int) -> None:
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr(
|
|
|
|
|
_netns.os,
|
|
|
|
|
'setns',
|
|
|
|
|
fake_setns,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
namespace_file: BinaryIO
|
|
|
|
|
with token_path.open('rb') as namespace_file:
|
|
|
|
|
inode: int = token_path.stat().st_ino
|
|
|
|
|
|
|
|
|
|
def fake_stat(path: Path) -> SimpleNamespace:
|
|
|
|
|
return SimpleNamespace(st_ino=inode + 1)
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr(
|
|
|
|
|
type(_netns._SELF_NETNS),
|
|
|
|
|
'stat',
|
|
|
|
|
fake_stat,
|
|
|
|
|
)
|
|
|
|
|
with pytest.raises(
|
|
|
|
|
RuntimeError,
|
|
|
|
|
match=f'{inode + 1}.*{inode}',
|
|
|
|
|
):
|
|
|
|
|
_netns.enter_netns(
|
|
|
|
|
namespace_file.fileno(),
|
|
|
|
|
# Deliberately differ from `fake_stat()`'s inode + 1.
|
|
|
|
|
inode,
|
|
|
|
|
)
|
2026-08-28 03:31:03 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_empty_netns_bootstrap_is_a_noop(
|
|
|
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
|
|
|
) -> None:
|
|
|
|
|
'''
|
|
|
|
|
Ordinary child startup must not attempt namespace entry.
|
|
|
|
|
|
|
|
|
|
Leave the optional capability unset and arm `enter_netns()` as a
|
|
|
|
|
failure sentinel. The bootstrap boundary must return without any
|
|
|
|
|
syscall or descriptor ownership work for existing spawn callers.
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
def fail_enter_netns(namespace_fd: int, inode: int) -> int:
|
|
|
|
|
'''
|
|
|
|
|
Reject namespace entry without an explicit capability.
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
raise AssertionError('empty bootstrap attempted netns entry')
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr(_entry, 'enter_netns', fail_enter_netns)
|
|
|
|
|
|
|
|
|
|
assert _entry._consume_netns_bootstrap(None) is None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize('namespace_fd', (-1, True, '1'))
|
|
|
|
|
def test_invalid_netns_fd_is_never_closed(
|
|
|
|
|
namespace_fd: object,
|
|
|
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
|
|
|
) -> None:
|
|
|
|
|
'''
|
|
|
|
|
Invalid descriptor values must not reach the OS close boundary.
|
|
|
|
|
|
|
|
|
|
Feed negative, boolean, and non-integer values through the atomic
|
|
|
|
|
capability. Preserve the namespace primitive's validation error
|
|
|
|
|
without letting `bool` alias stdout or allowing cleanup to mask the
|
|
|
|
|
primary failure.
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
entry_error = ValueError('invalid netns capability')
|
|
|
|
|
|
|
|
|
|
def fail_enter_netns(namespace_fd: int, inode: int) -> int:
|
|
|
|
|
'''
|
|
|
|
|
Raise the primary namespace bootstrap error.
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
raise entry_error
|
|
|
|
|
|
|
|
|
|
def fail_close(inherited_fd: int) -> None:
|
|
|
|
|
'''
|
|
|
|
|
Reject cleanup for a value that cannot be an owned FD.
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
raise AssertionError('invalid namespace FD reached close')
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr(_entry, 'enter_netns', fail_enter_netns)
|
|
|
|
|
monkeypatch.setattr(
|
|
|
|
|
_entry,
|
|
|
|
|
'os',
|
|
|
|
|
SimpleNamespace(close=fail_close),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
with pytest.raises(ValueError) as exc_info:
|
|
|
|
|
_entry._consume_netns_bootstrap(
|
|
|
|
|
(namespace_fd, 1), # type: ignore[arg-type]
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert exc_info.value is entry_error
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_netns_entry_error_survives_close_failure(
|
|
|
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
|
|
|
) -> None:
|
|
|
|
|
'''
|
|
|
|
|
Descriptor cleanup must not mask the primary bootstrap failure.
|
|
|
|
|
|
|
|
|
|
Raise a unique entry error for an oversized positive integer whose
|
|
|
|
|
cleanup also raises `OverflowError`. The entry error must escape
|
|
|
|
|
with cleanup context attached instead of being replaced by the
|
|
|
|
|
close failure.
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
namespace_fd: int = 1 << 100
|
|
|
|
|
entry_error = ValueError('invalid netns capability')
|
|
|
|
|
|
|
|
|
|
def fail_enter_netns(inherited_fd: int, inode: int) -> int:
|
|
|
|
|
'''
|
|
|
|
|
Raise the primary namespace bootstrap error.
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
assert inherited_fd == namespace_fd
|
|
|
|
|
raise entry_error
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr(_entry, 'enter_netns', fail_enter_netns)
|
|
|
|
|
|
|
|
|
|
with pytest.raises(ValueError) as exc_info:
|
|
|
|
|
_entry._consume_netns_bootstrap((namespace_fd, 1))
|
|
|
|
|
|
|
|
|
|
assert exc_info.value is entry_error
|
|
|
|
|
assert entry_error.__notes__
|
|
|
|
|
assert 'close inherited namespace FD' in entry_error.__notes__[0]
|
|
|
|
|
assert 'OverflowError' in entry_error.__notes__[0]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize('backend', ('mp', 'trio'))
|
|
|
|
|
def test_child_entry_consumes_netns_before_runtime(
|
|
|
|
|
backend: str,
|
|
|
|
|
tmp_path: Path,
|
|
|
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
|
|
|
) -> None:
|
|
|
|
|
'''
|
|
|
|
|
Child bootstrap must enter its netns before runtime side effects.
|
|
|
|
|
|
|
|
|
|
Give each child entrypoint an exclusively owned stand-in FD. Fake
|
|
|
|
|
only namespace entry and every later bootstrap boundary, requiring
|
|
|
|
|
the FD to remain open during entry but be closed before actor state,
|
|
|
|
|
logging, multiprocessing setup, frame hiding, or `trio.run()`.
|
|
|
|
|
This proves verified entry and capability release are one
|
|
|
|
|
synchronous prefix of both child startup paths.
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
token_path: Path = tmp_path / f'{backend}-netns'
|
|
|
|
|
token_path.touch()
|
|
|
|
|
namespace_fd: int = os.open(token_path, os.O_RDONLY)
|
|
|
|
|
expected_inode: int = os.fstat(namespace_fd).st_ino
|
|
|
|
|
events: list[str] = []
|
|
|
|
|
|
|
|
|
|
def fake_enter_netns(
|
|
|
|
|
inherited_fd: int,
|
|
|
|
|
inode: int,
|
|
|
|
|
) -> int:
|
|
|
|
|
'''
|
|
|
|
|
Record verified entry while the capability remains open.
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
assert os.fstat(inherited_fd).st_ino == expected_inode
|
|
|
|
|
assert inherited_fd == namespace_fd
|
|
|
|
|
assert inode == expected_inode
|
|
|
|
|
events.append('enter-netns')
|
|
|
|
|
return inode
|
|
|
|
|
|
|
|
|
|
def record(
|
|
|
|
|
event: str,
|
|
|
|
|
*args: object,
|
|
|
|
|
**kwargs: object,
|
|
|
|
|
) -> None:
|
|
|
|
|
'''
|
|
|
|
|
Record one post-entry operation after proving FD release.
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
_assert_fd_closed(namespace_fd)
|
|
|
|
|
events.append(event)
|
|
|
|
|
|
|
|
|
|
class ActorSpy:
|
|
|
|
|
'''
|
|
|
|
|
Record multiprocessing actor-state initialization.
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
loglevel = None
|
|
|
|
|
uid = ('netns-child', 'test')
|
|
|
|
|
_infected_aio = False
|
|
|
|
|
|
|
|
|
|
def __setattr__(
|
|
|
|
|
self,
|
|
|
|
|
name: str,
|
|
|
|
|
value: object,
|
|
|
|
|
) -> None:
|
|
|
|
|
'''
|
|
|
|
|
Observe the first multiprocessing entrypoint mutation.
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
if name == '_forkserver_info':
|
|
|
|
|
record('forkserver-info')
|
|
|
|
|
object.__setattr__(self, name, value)
|
|
|
|
|
|
|
|
|
|
class StateSpy:
|
|
|
|
|
'''
|
|
|
|
|
Record actor publication into runtime-global state.
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
def __setattr__(
|
|
|
|
|
self,
|
|
|
|
|
name: str,
|
|
|
|
|
value: object,
|
|
|
|
|
) -> None:
|
|
|
|
|
'''
|
|
|
|
|
Observe `_state._current_actor` publication.
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
record('runtime-state')
|
|
|
|
|
object.__setattr__(self, name, value)
|
|
|
|
|
|
|
|
|
|
def fake_current_process() -> str:
|
|
|
|
|
'''
|
|
|
|
|
Return one display value for multiprocessing startup logging.
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
return 'fake-child-process'
|
|
|
|
|
|
|
|
|
|
def fake_start_method(start_method: str) -> SimpleNamespace:
|
|
|
|
|
'''
|
|
|
|
|
Record multiprocessing setup after namespace entry.
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
record('start-method')
|
|
|
|
|
return SimpleNamespace(
|
|
|
|
|
current_process=fake_current_process,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def fake_actor(**kwargs: object) -> ActorSpy:
|
|
|
|
|
'''
|
|
|
|
|
Record Trio child actor construction after namespace entry.
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
record('actor-construction')
|
|
|
|
|
return ActorSpy()
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr(_entry, 'enter_netns', fake_enter_netns)
|
|
|
|
|
monkeypatch.setattr(_entry, '_state', StateSpy())
|
|
|
|
|
monkeypatch.setattr(
|
|
|
|
|
_entry._frame_stack,
|
|
|
|
|
'hide_runtime_frames',
|
|
|
|
|
partial(record, 'hide-frames'),
|
|
|
|
|
)
|
|
|
|
|
monkeypatch.setattr(
|
|
|
|
|
_entry.trio,
|
|
|
|
|
'run',
|
|
|
|
|
partial(record, 'trio-run'),
|
|
|
|
|
)
|
|
|
|
|
monkeypatch.setattr(
|
|
|
|
|
_entry,
|
|
|
|
|
'log',
|
|
|
|
|
SimpleNamespace(
|
|
|
|
|
info=partial(record, 'log'),
|
|
|
|
|
cancel=partial(record, 'log'),
|
|
|
|
|
error=partial(record, 'log'),
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
monkeypatch.setattr(
|
|
|
|
|
_spawn,
|
|
|
|
|
'try_set_start_method',
|
|
|
|
|
fake_start_method,
|
|
|
|
|
)
|
|
|
|
|
monkeypatch.setattr(
|
|
|
|
|
patches,
|
|
|
|
|
'apply_all',
|
|
|
|
|
partial(record, 'trio-patches'),
|
|
|
|
|
)
|
|
|
|
|
monkeypatch.setattr(_child, 'Actor', fake_actor)
|
|
|
|
|
monkeypatch.setattr(
|
|
|
|
|
_proctitle,
|
|
|
|
|
'set_actor_proctitle',
|
|
|
|
|
partial(record, 'proctitle'),
|
|
|
|
|
)
|
|
|
|
|
monkeypatch.setattr(
|
|
|
|
|
_child,
|
|
|
|
|
'_trio_main',
|
|
|
|
|
partial(record, 'trio-main'),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
actor: Any = ActorSpy()
|
|
|
|
|
bootstrap: tuple[int, int] = (
|
|
|
|
|
namespace_fd,
|
|
|
|
|
expected_inode,
|
|
|
|
|
)
|
|
|
|
|
if backend == 'mp':
|
|
|
|
|
_entry._mp_main(
|
|
|
|
|
actor,
|
|
|
|
|
[],
|
|
|
|
|
(None, None, None, None, None),
|
|
|
|
|
'mp_spawn',
|
|
|
|
|
netns_bootstrap=bootstrap,
|
|
|
|
|
)
|
|
|
|
|
first_runtime_event: str = 'forkserver-info'
|
|
|
|
|
terminal_event: str = 'trio-run'
|
|
|
|
|
else:
|
|
|
|
|
_child._actor_child_main(
|
|
|
|
|
uid=actor.uid,
|
|
|
|
|
loglevel=actor.loglevel,
|
|
|
|
|
parent_addr=None,
|
|
|
|
|
infect_asyncio=False,
|
|
|
|
|
netns_bootstrap=bootstrap,
|
|
|
|
|
)
|
|
|
|
|
first_runtime_event = 'trio-patches'
|
|
|
|
|
terminal_event = 'trio-main'
|
|
|
|
|
|
|
|
|
|
assert events[:2] == [
|
|
|
|
|
'enter-netns',
|
|
|
|
|
first_runtime_event,
|
|
|
|
|
]
|
|
|
|
|
assert events.count(terminal_event) == 1
|
|
|
|
|
_assert_fd_closed(namespace_fd)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize('backend', ('mp', 'trio'))
|
|
|
|
|
def test_child_entry_failure_closes_netns_fd_before_runtime(
|
|
|
|
|
backend: str,
|
|
|
|
|
tmp_path: Path,
|
|
|
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
|
|
|
) -> None:
|
|
|
|
|
'''
|
|
|
|
|
Failed namespace entry must close its FD and abort child startup.
|
|
|
|
|
|
|
|
|
|
Raise a unique error from the namespace boundary while a real
|
|
|
|
|
stand-in FD is open. Arm each entrypoint's first later operation as
|
|
|
|
|
a failure sentinel, then prove the original error escapes, the
|
|
|
|
|
descriptor is closed, and no actor, multiprocessing, frame, or
|
|
|
|
|
Trio runtime initialization begins.
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
token_path: Path = tmp_path / f'{backend}-failed-netns'
|
|
|
|
|
token_path.touch()
|
|
|
|
|
namespace_fd: int = os.open(token_path, os.O_RDONLY)
|
|
|
|
|
expected_inode: int = os.fstat(namespace_fd).st_ino
|
|
|
|
|
entry_error = RuntimeError('netns entry failed')
|
|
|
|
|
events: list[str] = []
|
|
|
|
|
|
|
|
|
|
def fail_enter_netns(
|
|
|
|
|
inherited_fd: int,
|
|
|
|
|
inode: int,
|
|
|
|
|
) -> int:
|
|
|
|
|
'''
|
|
|
|
|
Fail entry while proving the child-owned FD is still open.
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
assert os.fstat(inherited_fd).st_ino == expected_inode
|
|
|
|
|
assert inherited_fd == namespace_fd
|
|
|
|
|
assert inode == expected_inode
|
|
|
|
|
events.append('enter-netns')
|
|
|
|
|
raise entry_error
|
|
|
|
|
|
|
|
|
|
def fail_after_entry(*args: object, **kwargs: object) -> None:
|
|
|
|
|
'''
|
|
|
|
|
Reject any runtime operation after failed namespace entry.
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
raise AssertionError('child runtime started after netns failure')
|
|
|
|
|
|
|
|
|
|
class ActorSpy:
|
|
|
|
|
'''
|
|
|
|
|
Reject multiprocessing actor-state initialization.
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
loglevel = None
|
|
|
|
|
uid = ('failed-netns-child', 'test')
|
|
|
|
|
|
|
|
|
|
def __setattr__(
|
|
|
|
|
self,
|
|
|
|
|
name: str,
|
|
|
|
|
value: object,
|
|
|
|
|
) -> None:
|
|
|
|
|
'''
|
|
|
|
|
Reject the first multiprocessing entrypoint mutation.
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
if name == '_forkserver_info':
|
|
|
|
|
fail_after_entry()
|
|
|
|
|
object.__setattr__(self, name, value)
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr(_entry, 'enter_netns', fail_enter_netns)
|
|
|
|
|
monkeypatch.setattr(
|
|
|
|
|
_entry._frame_stack,
|
|
|
|
|
'hide_runtime_frames',
|
|
|
|
|
fail_after_entry,
|
|
|
|
|
)
|
|
|
|
|
monkeypatch.setattr(
|
|
|
|
|
_spawn,
|
|
|
|
|
'try_set_start_method',
|
|
|
|
|
fail_after_entry,
|
|
|
|
|
)
|
|
|
|
|
monkeypatch.setattr(
|
|
|
|
|
patches,
|
|
|
|
|
'apply_all',
|
|
|
|
|
fail_after_entry,
|
|
|
|
|
)
|
|
|
|
|
monkeypatch.setattr(_child, 'Actor', fail_after_entry)
|
|
|
|
|
monkeypatch.setattr(
|
|
|
|
|
_proctitle,
|
|
|
|
|
'set_actor_proctitle',
|
|
|
|
|
fail_after_entry,
|
|
|
|
|
)
|
|
|
|
|
monkeypatch.setattr(
|
|
|
|
|
_child,
|
|
|
|
|
'_trio_main',
|
|
|
|
|
fail_after_entry,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
actor: Any = ActorSpy()
|
|
|
|
|
bootstrap: tuple[int, int] = (
|
|
|
|
|
namespace_fd,
|
|
|
|
|
expected_inode,
|
|
|
|
|
)
|
|
|
|
|
with pytest.raises(RuntimeError) as exc_info:
|
|
|
|
|
if backend == 'mp':
|
|
|
|
|
_entry._mp_main(
|
|
|
|
|
actor,
|
|
|
|
|
[],
|
|
|
|
|
(None, None, None, None, None),
|
|
|
|
|
'mp_spawn',
|
|
|
|
|
netns_bootstrap=bootstrap,
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
_child._actor_child_main(
|
|
|
|
|
uid=actor.uid,
|
|
|
|
|
loglevel=actor.loglevel,
|
|
|
|
|
parent_addr=None,
|
|
|
|
|
infect_asyncio=False,
|
|
|
|
|
netns_bootstrap=bootstrap,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert exc_info.value is entry_error
|
|
|
|
|
assert events == ['enter-netns']
|
|
|
|
|
_assert_fd_closed(namespace_fd)
|