netns: nsenter в topology.sh, netns_common и документации (#56)
- topology.sh: ns_exec для всех ip -n <ns> и ip netns exec, коды 0/1/3/4. - netns_common.py: ns_exec_mode()/ns_exec_direct(), addrs/links/link_is_up через ns_sh. - docker-compose.test.yml: проброс AWG_NS_EXEC_MODE в netns-сервис. - tests/netns/README.md: режимы входа в netns, коды возврата, новый pitfall. Refs #21, Refs #15, Refs #56
This commit is contained in:
@@ -11,6 +11,7 @@ from __future__ import annotations
|
||||
import json
|
||||
import os
|
||||
import pathlib
|
||||
import shutil
|
||||
import subprocess
|
||||
import time
|
||||
from typing import Any, Iterable
|
||||
@@ -61,6 +62,10 @@ class StandError(RuntimeError):
|
||||
"""Ошибка стенда (не теста): топология/утилиты не отвечают как ожидается."""
|
||||
|
||||
|
||||
# Кэш выбранного режима входа в netns (ip|nsenter), см. ns_exec_mode().
|
||||
_NS_EXEC_MODE: str | None = None
|
||||
|
||||
|
||||
def sh(args: Iterable[str], check: bool = True, timeout: float = 20.0) -> subprocess.CompletedProcess:
|
||||
"""Запуск команды в корневом netns."""
|
||||
cp = subprocess.run(
|
||||
@@ -75,9 +80,56 @@ def sh(args: Iterable[str], check: bool = True, timeout: float = 20.0) -> subpro
|
||||
return cp
|
||||
|
||||
|
||||
def ns_exec_direct(
|
||||
mode: str,
|
||||
ns: str,
|
||||
args: Iterable[str],
|
||||
check: bool = True,
|
||||
timeout: float = 20.0,
|
||||
) -> subprocess.CompletedProcess:
|
||||
"""Одна попытка входа в netns выбранным способом (ip|nsenter)."""
|
||||
cmd = (["nsenter", f"--net=/var/run/netns/{ns}"] if mode == "nsenter" else ["ip", "netns", "exec", ns]) + list(args)
|
||||
return sh(cmd, check=check, timeout=timeout)
|
||||
|
||||
|
||||
def _probe_ns_exec(mode: str) -> bool:
|
||||
probe = os.environ.get("AWG_NETNS_PREFIX", "awgt21") + "-probe"
|
||||
if sh(["ip", "netns", "add", probe], check=False).returncode != 0:
|
||||
return False
|
||||
try:
|
||||
return ns_exec_direct(mode, probe, ["sh", "-c", "true"], check=False, timeout=10.0).returncode == 0
|
||||
finally:
|
||||
sh(["ip", "netns", "delete", probe], check=False)
|
||||
|
||||
|
||||
def ns_exec_mode() -> str:
|
||||
"""Режим входа в netns: ip | nsenter (issue #56).
|
||||
|
||||
`ip netns exec` (iproute2 6.x) перемонтирует /sys и в контейнере падает с
|
||||
"mount of /sys failed: Operation not permitted"; nsenter входит в
|
||||
/var/run/netns/<ns> без перемонтирования. Режим кэшируется на процесс,
|
||||
AWG_NS_EXEC_MODE=ip|nsenter|auto переопределяет выбор.
|
||||
"""
|
||||
global _NS_EXEC_MODE
|
||||
if _NS_EXEC_MODE is None:
|
||||
want = os.environ.get("AWG_NS_EXEC_MODE", "auto")
|
||||
if want in ("ip", "nsenter"):
|
||||
_NS_EXEC_MODE = want
|
||||
elif _probe_ns_exec("ip"):
|
||||
_NS_EXEC_MODE = "ip"
|
||||
elif shutil.which("nsenter") and _probe_ns_exec("nsenter"):
|
||||
_NS_EXEC_MODE = "nsenter"
|
||||
else:
|
||||
raise StandError(
|
||||
"ни 'ip netns exec', ни 'nsenter --net' не работают в этом окружении "
|
||||
"(нет CAP_SYS_ADMIN/NET_ADMIN) — issue #56"
|
||||
)
|
||||
return _NS_EXEC_MODE
|
||||
|
||||
|
||||
def ns_sh(ns: str, args: Iterable[str], check: bool = True, timeout: float = 20.0) -> subprocess.CompletedProcess:
|
||||
"""Запуск команды внутри указанного netns."""
|
||||
return sh(["ip", "netns", "exec", ns, *args], check=check, timeout=timeout)
|
||||
"""Запуск команды внутри указанного netns (ip или nsenter — см. ns_exec_mode)."""
|
||||
return ns_exec_direct(ns_exec_mode(), ns, args, check=check, timeout=timeout)
|
||||
|
||||
|
||||
def ns_exists(ns: str) -> bool:
|
||||
@@ -86,15 +138,15 @@ def ns_exists(ns: str) -> bool:
|
||||
|
||||
|
||||
def addrs(ns: str) -> str:
|
||||
return sh(["ip", "-n", ns, "-br", "addr", "show"]).stdout
|
||||
return ns_sh(ns, ["ip", "-br", "addr", "show"]).stdout
|
||||
|
||||
|
||||
def links(ns: str) -> str:
|
||||
return sh(["ip", "-n", ns, "-br", "link", "show"]).stdout
|
||||
return ns_sh(ns, ["ip", "-br", "link", "show"]).stdout
|
||||
|
||||
|
||||
def link_is_up(ns: str, ifname: str) -> bool:
|
||||
cp = sh(["ip", "-n", ns, "-br", "link", "show", ifname], check=False)
|
||||
cp = ns_sh(ns, ["ip", "-br", "link", "show", ifname], check=False)
|
||||
return cp.returncode == 0 and "UP" in cp.stdout
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user