fix(relay): clarify opt-in plugin layering
Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
This commit is contained in:
@@ -1905,9 +1905,8 @@ def _configured_plugin_inputs(
|
|||||||
"Hermes [[dynamic_plugins]] records are unsupported; use Relay "
|
"Hermes [[dynamic_plugins]] records are unsupported; use Relay "
|
||||||
"[[plugins.dynamic]] records"
|
"[[plugins.dynamic]] records"
|
||||||
)
|
)
|
||||||
plugins_section = config.get("plugins")
|
|
||||||
dynamic_plugins: list[Any] = []
|
dynamic_plugins: list[Any] = []
|
||||||
if plugins_section:
|
if "plugins" in config:
|
||||||
dynamic_plugins = relay.plugin.load_dynamic_plugin_activation_specs(
|
dynamic_plugins = relay.plugin.load_dynamic_plugin_activation_specs(
|
||||||
config_path
|
config_path
|
||||||
)
|
)
|
||||||
@@ -1923,6 +1922,7 @@ def _configured_plugin_inputs(
|
|||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _flush_relay_subscribers(relay: Any) -> None:
|
def _flush_relay_subscribers(relay: Any) -> None:
|
||||||
"""Flush Relay without blocking an asyncio event-loop thread."""
|
"""Flush Relay without blocking an asyncio event-loop thread."""
|
||||||
_resolve_plugin_awaitable(relay.subscribers.flush_async())
|
_resolve_plugin_awaitable(relay.subscribers.flush_async())
|
||||||
|
|||||||
@@ -43,10 +43,15 @@ behalf.
|
|||||||
|
|
||||||
Relay plugin activation is owned by the native runtime and remains explicitly
|
Relay plugin activation is owned by the native runtime and remains explicitly
|
||||||
opt-in. Set `HERMES_NEMO_RELAY_PLUGINS_TOML` to a selected `plugins.toml` to
|
opt-in. Set `HERMES_NEMO_RELAY_PLUGINS_TOML` to a selected `plugins.toml` to
|
||||||
activate configured middleware, exporters, or dynamic plugins. When it is
|
activate configured middleware, exporters, or dynamic plugins. When the
|
||||||
unset, Hermes does not invoke Relay's plugin initializer or trigger Relay
|
variable is unset, Hermes does not invoke Relay's plugin initializer, so Relay
|
||||||
plugin configuration discovery. Invalid explicit configuration is reported
|
does not perform plugin configuration discovery or layering. When it is set
|
||||||
and Hermes continues without native plugin activation.
|
and the selected file loads successfully, Relay performs its normal static
|
||||||
|
`plugins.toml` discovery and layers the selected static configuration over the
|
||||||
|
discovered configuration. Dynamic `[[plugins.dynamic]]` records are loaded
|
||||||
|
from the selected file only. If the selected file cannot be loaded, Hermes
|
||||||
|
reports the error and does not invoke Relay initialization or fall back to
|
||||||
|
ambient discovery.
|
||||||
|
|
||||||
Hermes core owns one Relay host and one isolated Relay session scope per Hermes
|
Hermes core owns one Relay host and one isolated Relay session scope per Hermes
|
||||||
session. Core lifecycle producers use
|
session. Core lifecycle producers use
|
||||||
|
|||||||
@@ -215,6 +215,32 @@ def test_malformed_explicit_config_does_not_fall_back_to_discovery(
|
|||||||
host.shutdown()
|
host.shutdown()
|
||||||
|
|
||||||
|
|
||||||
|
def test_present_plugins_section_is_validated_even_when_falsey(
|
||||||
|
tmp_path,
|
||||||
|
monkeypatch,
|
||||||
|
caplog,
|
||||||
|
):
|
||||||
|
config = tmp_path / "plugins.toml"
|
||||||
|
config.write_text("plugins = []", encoding="utf-8")
|
||||||
|
monkeypatch.setenv(relay_runtime.RELAY_PLUGINS_CONFIG_ENV, str(config))
|
||||||
|
relay = _FakeRelay()
|
||||||
|
|
||||||
|
def reject_invalid_plugins(_config_path):
|
||||||
|
raise ValueError("'plugins' must be a table")
|
||||||
|
|
||||||
|
relay.plugin.load_dynamic_plugin_activation_specs = reject_invalid_plugins
|
||||||
|
|
||||||
|
with caplog.at_level("WARNING"):
|
||||||
|
host = relay_runtime.RelayRuntime(relay=relay, profile_key="profile")
|
||||||
|
try:
|
||||||
|
assert not host.managed_execution_enabled()
|
||||||
|
assert relay.events == []
|
||||||
|
assert "'plugins' must be a table" in caplog.text
|
||||||
|
assert "continuing without Relay plugins" in caplog.text
|
||||||
|
finally:
|
||||||
|
host.shutdown()
|
||||||
|
|
||||||
|
|
||||||
def test_two_profile_hosts_initialize_once_and_clear_after_final_shutdown(
|
def test_two_profile_hosts_initialize_once_and_clear_after_final_shutdown(
|
||||||
explicit_static_config,
|
explicit_static_config,
|
||||||
):
|
):
|
||||||
@@ -767,6 +793,67 @@ mode = "overwrite"
|
|||||||
assert not (atof_dir / "events.jsonl").exists()
|
assert not (atof_dir / "events.jsonl").exists()
|
||||||
|
|
||||||
|
|
||||||
|
def test_real_binding_layers_project_config_after_explicit_opt_in(
|
||||||
|
tmp_path,
|
||||||
|
monkeypatch,
|
||||||
|
):
|
||||||
|
relay = pytest.importorskip("nemo_relay")
|
||||||
|
if getattr(relay, "_native", None) is None:
|
||||||
|
pytest.skip("NeMo Relay native binding is unavailable on this platform")
|
||||||
|
|
||||||
|
project_root = tmp_path / "project"
|
||||||
|
working_directory = project_root / "workspace"
|
||||||
|
config_directory = project_root / ".nemo-relay"
|
||||||
|
selected_directory = tmp_path / "selected-config"
|
||||||
|
atof_dir = tmp_path / "atof"
|
||||||
|
working_directory.mkdir(parents=True)
|
||||||
|
config_directory.mkdir()
|
||||||
|
selected_directory.mkdir()
|
||||||
|
(config_directory / "plugins.toml").write_text(
|
||||||
|
f"""
|
||||||
|
version = 1
|
||||||
|
|
||||||
|
[[components]]
|
||||||
|
kind = "observability"
|
||||||
|
enabled = true
|
||||||
|
|
||||||
|
[components.config]
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[components.config.atof]
|
||||||
|
enabled = true
|
||||||
|
|
||||||
|
[[components.config.atof.sinks]]
|
||||||
|
type = "file"
|
||||||
|
output_directory = "{atof_dir}"
|
||||||
|
filename = "events.jsonl"
|
||||||
|
mode = "overwrite"
|
||||||
|
""".strip(),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
selected_config = selected_directory / "plugins.toml"
|
||||||
|
selected_config.write_text("version = 1", encoding="utf-8")
|
||||||
|
xdg_config_home = tmp_path / "xdg"
|
||||||
|
xdg_config_home.mkdir()
|
||||||
|
monkeypatch.chdir(working_directory)
|
||||||
|
monkeypatch.setenv("XDG_CONFIG_HOME", str(xdg_config_home))
|
||||||
|
monkeypatch.setenv(
|
||||||
|
relay_runtime.RELAY_PLUGINS_CONFIG_ENV,
|
||||||
|
str(selected_config),
|
||||||
|
)
|
||||||
|
relay.plugin.clear()
|
||||||
|
|
||||||
|
host = relay_runtime.RelayRuntime(relay=relay, profile_key="profile")
|
||||||
|
try:
|
||||||
|
assert host.managed_execution_enabled()
|
||||||
|
host.ensure_session({"session_id": "native-layered-plugins"})
|
||||||
|
finally:
|
||||||
|
host.shutdown()
|
||||||
|
relay_runtime._reset_for_tests()
|
||||||
|
|
||||||
|
assert (atof_dir / "events.jsonl").is_file()
|
||||||
|
|
||||||
|
|
||||||
def test_real_binding_loads_explicit_config_and_exports_native_activity(
|
def test_real_binding_loads_explicit_config_and_exports_native_activity(
|
||||||
tmp_path,
|
tmp_path,
|
||||||
monkeypatch,
|
monkeypatch,
|
||||||
|
|||||||
Reference in New Issue
Block a user