Skip to content

Extract GraphProviderPlugin to loadable .so plugin#289

Open
bburda wants to merge 16 commits intomainfrom
feature/extract-graph-provider-plugin-v2
Open

Extract GraphProviderPlugin to loadable .so plugin#289
bburda wants to merge 16 commits intomainfrom
feature/extract-graph-provider-plugin-v2

Conversation

@bburda
Copy link
Collaborator

@bburda bburda commented Mar 18, 2026

Pull Request

Summary

Extract GraphProviderPlugin from a hardcoded built-in plugin into a dynamically loadable .so in a separate colcon package (ros2_medkit_graph_provider). Users can now opt-in/out of the graph provider without recompiling the gateway.

Key changes:

  • Extend PluginContext with get_entity_snapshot(), list_all_faults(), register_sampler() - replacing the dynamic_cast<GatewayNode*> pattern
  • Create new package src/ros2_medkit_plugins/ros2_medkit_graph_provider/ following the beacon plugin MODULE pattern
  • Scope diagnostic_msgs dependency to the plugin package only
  • Launch file resolves .so path via ament_index with graceful fallback when not installed
  • Bump PLUGIN_API_VERSION to 4 (PluginContext vtable changes)

Issue


Type

  • Bug fix
  • New feature or tests
  • Breaking change
  • Documentation only

Testing

  • Unit tests: 21 FakePluginContext-based tests migrated to new package (all pass)
  • Integration tests: New test_graph_provider_plugin.test.py validates plugin loading, capability registration, and graph endpoint responses
  • Gateway unit tests: Removed plugin-dependent assertions from test_gateway_node.cpp
  • Linters: All pass (clang-format, cmake-lint, ament-copyright, cppcheck, etc.)
  • Full suite: 1789 unit tests, 0 failures

Breaking changes

  • PLUGIN_API_VERSION bumped from 3 to 4 - external plugins must be recompiled
  • diagnostic_msgs removed from ros2_medkit_gateway dependencies
  • Graph provider no longer built-in - must be loaded via YAML config (launch file handles this automatically)

Checklist

  • Breaking changes are clearly described (and announced in docs / changelog if needed)
  • Tests were added or updated if needed
  • Docs were updated if behavior or public API changed

bburda added 13 commits March 18, 2026 20:20
Prepares for PluginContext vtable changes (new virtual methods for
entity snapshot, fault listing, and sampler registration).

Refs #277
…ng, sampler registration

Add three new virtual methods to PluginContext:
- get_entity_snapshot(): bulk entity access via IntrospectionInput
- list_all_faults(): query all faults without direct FaultManager access
- register_sampler(): register cyclic subscription samplers

These replace the dynamic_cast<GatewayNode*> pattern used by plugins
that need entity cache, fault data, or sampler registration.

Refs #277
Replace direct GatewayNode access with PluginContext methods:
- get_entity_snapshot() instead of get_thread_safe_cache()
- list_all_faults() instead of get_fault_manager()->list_faults()
- register_sampler() instead of get_sampler_registry()->register_sampler()

Remove dynamic_cast<GatewayNode*> and GatewayNode/FaultManager includes.

Update tests to set FakePluginContext::entity_snapshot_ so the HTTP-path
rebuild in get_cached_or_built_graph() uses the correct entity state.

Refs #277
New colcon package for the graph provider plugin as a loadable .so.
Extracted from ros2_medkit_gateway - the plugin is now independently
buildable and optional. Scopes diagnostic_msgs dependency to this
package only.

Refs #277
Move FakePluginContext-based unit tests from ros2_medkit_gateway.
GatewayNode-based tests will be migrated to integration tests separately.

Make PluginContext::send_error/send_json inline so the new package can
link the test without depending on gateway_lib at link time.

Refs #277
Remove graph_provider_plugin sources, header, tests, and hardcoded
add_plugin() call. Remove diagnostic_msgs dependency (only used by
graph provider). The plugin now lives in ros2_medkit_graph_provider.

Refs #277
Launch file computes absolute path to graph provider .so using
ament_index. Plugin path resolution uses explicit absolute paths
only - no magic auto-resolution in the loader.

Gateway starts gracefully even if the graph provider package is not
installed (launch file catches PackageNotFoundError).

Refs #277
Unit tests must not depend on specific plugins being loaded.
Graph provider assertions moved to integration tests.

Refs #277
… linking

Revert the inlining of PluginContext::send_error/send_json that broke
beacon plugin tests (redefinition errors). Restore out-of-line
definitions in plugin_context.cpp delegating to HandlerContext.

For the graph provider MODULE target, use --unresolved-symbols=ignore-all
so send_error/send_json resolve at runtime from the host process.
Add stub implementations in graph provider unit tests (matching beacon
plugin test pattern).

Refs #277
Launch gateway with graph provider .so loaded dynamically via hybrid
discovery mode. Verify:
- Plugin loads and registers x-medkit-graph capability on functions
- GET /functions/{id}/x-medkit-graph returns valid graph document
- Nonexistent function returns 404
- Graph nodes reflect apps hosted by the function

Refs #277
Add ros2_medkit_graph_provider package docs, configuration examples,
and new PluginContext API methods.

Refs #277
- Add missing test_depend for ros2_medkit_graph_provider in integration
  tests package.xml (CI blocker - colcon didn't guarantee build order)
- Restore default plugins: [""] in gateway_params.yaml (was causing
  ERROR logs on standalone launches without gateway.launch.py)
- Launch file now injects both plugins list and path when graph provider
  is installed, with visible print() when package is not found
- Fix stale "1 built-in plugin" log message (no built-in plugins remain)
- Fix API version in plugin-system.rst docs (was "1", now "4")
- Clarify ABI compat note: rebuild required for version check even
  though new PluginContext methods have default no-op implementations
- Declare plugin path parameters as read_only to prevent runtime changes

Refs #277
The default no-op register_sampler() took std::function by value,
triggering performance-unnecessary-value-param on every TU that
includes plugin_context.hpp. Take by const ref instead.

Refs #277
@bburda bburda self-assigned this Mar 19, 2026
@bburda bburda marked this pull request as ready for review March 19, 2026 08:19
Copilot AI review requested due to automatic review settings March 19, 2026 08:19
@bburda
Copy link
Collaborator Author

bburda commented Mar 19, 2026

Hi @eclipse0922 could you check this? I moved your plugin to a separate package.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extracts the GraphProviderPlugin from the gateway binary into an optional, dynamically loaded MODULE .so shipped as a new colcon package (ros2_medkit_graph_provider). It extends the plugin context API so plugins can access entity snapshots, faults, and cyclic sampler registration without dynamic_cast<GatewayNode*>, and updates docs/tests accordingly.

Changes:

  • Introduces ros2_medkit_graph_provider package that builds and installs libros2_medkit_graph_provider_plugin.so with proper extern "C" exports.
  • Extends PluginContext with get_entity_snapshot(), list_all_faults(), and register_sampler() and wires these into the gateway’s concrete plugin context.
  • Updates gateway launch/config/docs/tests to load the graph provider plugin by path and validates functionality via a new integration test.

Reviewed changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/ros2_medkit_plugins/ros2_medkit_graph_provider/CMakeLists.txt Builds/installs graph provider MODULE plugin + unit test target.
src/ros2_medkit_plugins/ros2_medkit_graph_provider/package.xml New plugin package manifest + dependencies.
src/ros2_medkit_plugins/ros2_medkit_graph_provider/include/ros2_medkit_graph_provider/graph_provider_plugin.hpp Public header for extracted plugin.
src/ros2_medkit_plugins/ros2_medkit_graph_provider/src/graph_provider_plugin.cpp Plugin implementation updated to use PluginContext accessors instead of GatewayNode casts.
src/ros2_medkit_plugins/ros2_medkit_graph_provider/src/graph_provider_plugin_exports.cpp dlopen export surface (plugin_api_version, create_plugin, provider getter).
src/ros2_medkit_plugins/ros2_medkit_graph_provider/test/test_graph_provider_plugin.cpp Plugin tests updated/migrated for new context API and package layout.
src/ros2_medkit_gateway/include/ros2_medkit_gateway/plugins/plugin_context.hpp Adds new plugin context methods (snapshot/faults/sampler registration).
src/ros2_medkit_gateway/src/plugins/plugin_context.cpp Implements new context methods for the gateway runtime.
src/ros2_medkit_gateway/include/ros2_medkit_gateway/plugins/plugin_types.hpp Bumps PLUGIN_API_VERSION to 4.
src/ros2_medkit_gateway/src/gateway_node.cpp Removes built-in graph plugin injection; adds read-only plugin path parameter descriptor; passes sampler registry into plugin context.
src/ros2_medkit_gateway/CMakeLists.txt Drops graph provider sources and diagnostic_msgs dependency from core gateway build/tests.
src/ros2_medkit_gateway/package.xml Removes diagnostic_msgs dependency from gateway package.
src/ros2_medkit_gateway/launch/gateway.launch.py Resolves graph provider .so path via ament index and injects parameters when installed.
src/ros2_medkit_gateway/config/gateway_params.yaml Adds graph provider plugin parameters and notes optional loading via launch.
src/ros2_medkit_gateway/test/test_gateway_node.cpp Removes assertions that depended on built-in graph provider presence.
src/ros2_medkit_integration_tests/test/features/test_graph_provider_plugin.test.py New end-to-end test verifying dynamic plugin loading and endpoint behavior.
src/ros2_medkit_integration_tests/package.xml Adds ros2_medkit_graph_provider as a test dependency.
docs/tutorials/plugin-system.rst Documents new PluginContext APIs and the optional graph provider plugin.

Comment on lines +62 to +66
# Allow unresolved symbols - they resolve from the host process at runtime
target_link_options(ros2_medkit_graph_provider_plugin PRIVATE
-Wl,--unresolved-symbols=ignore-all
)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intentional. MODULE targets with --unresolved-symbols=ignore-in-shared-libs still fail because the unresolved symbols are in the MODULE's own object files, not in linked shared libs. ignore-all is required here because gateway symbols (PluginContext::send_error, send_json, GatewayPlugin::log_info etc.) resolve at runtime from the host gateway_node executable via ENABLE_EXPORTS ON + RTLD_LOCAL. This is the same pattern used by all plugin .so targets in this workspace.

@eclipse0922
Copy link
Contributor

Hi @eclipse0922 could you check this? I moved your plugin to a separate package.

Thanks for the changes, I’ll review it.

@eclipse0922
Copy link
Contributor

Thanks for taking this on. I think extracting GraphProviderPlugin into its own package is a good direction overall. It makes the graph feature feel much more optional at deployment time, and I also like that the plugin-specific tests moved out of the gateway unit test layer.

I have one structural concern. After the extraction, the configuration boundary feels a bit inconsistent. The gateway plugin system models plugin config as plugins.<name>.<key>, and this PR/documentation also moved the graph provider settings into that shape. But the plugin itself still seems to read graph_provider.* node parameters directly instead of consuming the plugin config through configure().

Because of that, the package split looks right conceptually, but the runtime/config boundary does not feel fully aligned yet. I think it would be better if the extracted plugin followed the same config path as the rest of the plugin system, so the docs, YAML shape, and runtime behavior all match.

Other than that, the overall extraction looks solid to me. My only non-blocking note is that PluginContext is still growing around graph-provider needs, so this is a nice packaging split already, even if the plugin API boundary is not fully narrowed yet.

bburda added 2 commits March 19, 2026 15:33
- Fix pre-existing bug: load_parameters() now reads from JSON config
  passed to configure() instead of mismatched ROS parameter names
  (graph_provider.* vs plugins.graph_provider.*)
- Add unit test for cyclic subscription sampler registration
- Add config override test via configure() JSON
- Add diagnostics subscription test with rclcpp::Node
- Add @verifies REQ_INTEROP_003 tags to key route tests
- Launch file verifies .so exists on disk before injecting path

Refs #277
ROS_DOMAIN_ID=70 was already used by ros2_medkit_param_beacon,
causing potential DDS cross-talk when CTest runs in parallel.

Refs #277
@bburda
Copy link
Collaborator Author

bburda commented Mar 19, 2026

@eclipse0922 Thanks for the review! Good catch on the config boundary - you're right that load_parameters() was reading graph_provider.* params directly instead of going through configure(). Fixed in 5260ee0: the plugin now reads all config from the JSON passed to configure() by the plugin framework, so docs, YAML shape (plugins.graph_provider.*), and runtime behavior are all aligned.

Regarding PluginContext growing around graph-provider needs - agreed. The three new methods (get_entity_snapshot, list_all_faults, register_sampler) are generic enough for any plugin that needs entity data, fault visibility, or cyclic subscription support, but I'll keep an eye on whether the interface stays balanced as more plugins get extracted.

@bburda bburda requested a review from mfaferek93 March 19, 2026 17:43
Resolve conflicts between graph provider extraction and locking feature:
- plugin_context.hpp: keep both entity snapshot/faults/sampler and lock methods
- plugin_context.cpp: keep both includes and implementations
- test_graph_provider_plugin.cpp: add lock method stubs to FakePluginContext

Adapt graph provider CMakeLists.txt to use ros2_medkit_cmake package
(replaces removed cmake/ directory path from PR #294).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Extract GraphProviderPlugin to a loadable .so plugin

3 participants