[Bug]: Deltaproxy sub-proxies share one module object, so module-level state in proxy/execution modules leaks across all controlled minions
Los mantenedores suelen responder en 3 días
Evaluación
- Dificultad
- 4/5
- Tiempo estimado
- 3-5 días
- Aptitud para principiantes
- 35/100
Línea de trabajo
Start by reading salt/loader/lazy.py, especially LazyLoader._load_module() and subproxy_post_master_init(), to see how loaded_base_name builds the module namespace and how sys.modules reuses module objects. Reproduce with the fakedev proxy/module from the report, comparing fakedev.host, fakedev.context_host and grains.item device_host across minion1-3. Done means each sub-proxy has isolated module globals and every controlled minion reports its own host.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
What happened?
Description
This is a follow-up to #68248. That issue was closed by #69426, which re-packs __grains__ into each sub-proxy's loaders. The underlying problem is broader and that change does not fix it: with deltaproxy, every sub-proxy's LazyLoader resolves a given module to the same Python module object. Any module-level state (connection handles, DETAILS dicts, caches) is therefore shared by all controlled minions, and the last sub-proxy to run init() wins. Every execution module that reaches the device through __proxy__ then returns the data of that one device, which is why grains, and in fact any function, return identical data for all controlled minions.
I applied #69426 to a 3006.9 install and the behaviour remains, as noted in #68248.
Root cause
In salt/loader/lazy.py, LazyLoader._load_module() loads modules with the deprecated spec.loader.load_module(), under a namespace built from loaded_base_name and the tag, e.g. salt.loaded.ext.proxy.fakedev. Every sub-proxy loader created in subproxy_post_master_init() uses the same loaded_base_name (salt.loaded), so they all compute the same namespace. When that name is already in sys.modules, load_module() re-executes the code into the existing module object instead of creating a new one.
The packed dunders (__context__, __grains__, __pillar__, __proxy__, ...) are injected as NamedLoaderContext objects that resolve through the active loader, so those stay per sub-proxy. Plain module globals do not.
Setup
Deltaproxy config (/etc/salt/proxy):
master: 127.0.0.1
metaproxy: deltaproxy
enable_fqdns_grains: False
multiprocessing: False # also reproduces with True
skip_connect_on_init: True
Pillar top.sls:
base:
dp_control:
- control
minion1:
- p_minion1
minion2:
- p_minion2
minion3:
- p_minion3
control.sls:
proxy:
proxytype: deltaproxy
ids: [minion1, minion2, minion3]
p_minionN.sls (N = 1, 2, 3):
proxy:
proxytype: fakedev
host: 10.0.0.1N
salt://_proxy/fakedev.py. It follows the common pattern of keeping connection state in a module global, and also stores the host in __context__ for comparison:
__proxyenabled__ = ["fakedev"]
DETAILS = {}
def __virtual__():
return "fakedev"
def init(opts):
DETAILS["host"] = opts["proxy"]["host"]
DETAILS["initialized"] = True
__context__["fakedev"] = {"host": opts["proxy"]["host"]}
def initialized():
return DETAILS.get("initialized", False)
def shutdown(opts):
return True
def grains():
return {"device_host": DETAILS.get("host")}
def host():
return DETAILS.get("host")
def context_host():
return __context__["fakedev"]["host"]
salt://_modules/fakedev.py:
__proxyenabled__ = ["fakedev"]
def __virtual__():
return "fakedev"
def host():
return __proxy__["fakedev.host"]()
def context_host():
return __proxy__["fakedev.context_host"]()
Steps to Reproduce the behavior
salt-proxy --proxyid=dp_control -d
salt-key -A
salt 'minion*' fakedev.host --out=txt
salt 'minion*' fakedev.context_host --out=txt
salt 'minion*' grains.item device_host --out=txt
salt 'minion*' pillar.get proxy:host --out=txt
Output:
# module global: every minion reports the last-initialised device
minion1: 10.0.0.13
minion2: 10.0.0.13
minion3: 10.0.0.13
# __context__: correct per minion
minion1: 10.0.0.11
minion2: 10.0.0.12
minion3: 10.0.0.13
# grains derived from the proxy module: same wrong value everywhere
minion1: {'device_host': '10.0.0.13'}
minion2: {'device_host': '10.0.0.13'}
minion3: {'device_host': '10.0.0.13'}
# pillar: correct per minion
minion1: 10.0.0.11
minion2: 10.0.0.12
minion3: 10.0.0.13
The same result occurs with multiprocessing: True, because forked job processes inherit the already-shared module.
Expected behavior
Each sub-proxy should have isolated module state, so each controlled minion talks to and reports on its own device, as it would under a regular per-device salt-proxy.
Additional context
Related: #68248 (closed by #69426, which only addresses __grains__ re-packing).
Type of salt install
Official deb
Major version
3006.x
What supported OS are you seeing the problem on? Can select multiple. (If bug appears on an unsupported OS, please open a GitHub Discussion instead)
ubuntu-24.04, ubuntu-22.04
salt --versions-report output
Salt Version:
Salt: 3006.9
Python Version:
Python: 3.10.14 (main, Jun 26 2024, 11:44:37) [GCC 11.2.0]
Dependency Versions:
cffi: 1.14.6
cherrypy: unknown
cryptography: 42.0.5
dateutil: 2.8.1
docker-py: Not Installed
gitdb: Not Installed
gitpython: Not Installed
Jinja2: 3.1.4
libgit2: Not Installed
looseversion: 1.0.2
M2Crypto: Not Installed
Mako: Not Installed
msgpack: 1.0.2
msgpack-pure: Not Installed
mysql-python: Not Installed
packaging: 22.0
pycparser: 2.21
pycrypto: Not Installed
pycryptodome: 3.19.1
pygit2: Not Installed
python-gnupg: 0.4.8
PyYAML: 6.0.1
PyZMQ: 23.2.0
relenv: 0.17.0
smmap: Not Installed
timelib: 0.2.4
Tornado: 4.5.3
ZMQ: 4.3.4
System Versions:
dist: ubuntu 22.04.4 jammy
locale: utf-8
machine: x86_64
release: 5.15.0-185-generic
system: Linux
version: Ubuntu 22.04.4 jammy
- Lenguaje dominante
- Python
- Estrellas
- 15.8k
- Forks
- 5.6k
- Merge medio
- 5 d 9 h
- PR fusionados (30 d)
- 99
Preparar el entorno
- Sin Dockerfile ni archivo de Docker Compose
- Tiene una plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de saltstack/salt
-
[Bug]: Scheduled returns scan all accepted minion keys causing extreme loadPosiblemente ocupada Un pull request vinculado a esta issue está abierto o ya se fusionó. Abiertobug needs-triage
Dificultad 2/5 1-3 horas Aptitud para principiantes 76/100
saltstack/salt#70789 · 1 comentario ·
Los mantenedores suelen responder en 3 días
-
[Bug]: pip.installed with upgrade=True upgrades a package but returns an empty changes dictionaryPosiblemente ocupada @ITJamie la tomó hace 1 día. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
Los mantenedores suelen responder en 3 días
-
bug needs-triage
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
Los mantenedores suelen responder en 3 días
-
[Bug]: Minion with file_client: local crashes on connect: 'AsyncPillar' object has no attribute 'aclose'Posiblemente ocupada @shawnrruss la tomó hace 9 días. Abiertobug needs-triage
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
Los mantenedores suelen responder en 3 días
-
[Bug]: mine.update clear=True raises UnboundLocalError on the master and stores nothingPosiblemente ocupada @cestercian la tomó hace 11 días. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 74/100
saltstack/salt#70622 · 1 comentario ·
Los mantenedores suelen responder en 3 días
Todos los issues de saltstack/salt
Issues similares
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 82/100
btclib-org/btclib-wallet#872 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 87/100
btclib-org/btclib#3093 ·
Los mantenedores suelen responder en 1 día
-
good first issue
Dificultad 2/5 1-3 horas Aptitud para principiantes 85/100
patrickballard/wheelgap#269 ·
-
priority: low python
Dificultad 2/5 1-3 horas Aptitud para principiantes 88/100
LearningCircuit/local-deep-research#8635 ·
Los mantenedores suelen responder en 1 día
-
README says the land/sea mask must match the XS grid; the demo mask does not, and Step 4 worksAbiertodocumentation
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
buckai-observatory/Automated-Cloud-Removal-and-Seamless-Satellite-Image-Mosaicking-Pipeline#52 ·
Los mantenedores suelen responder en 1 día