NMS-19875: Topology assets: image storage for view backgrounds and custom node icons#8563
Open
marshallmassengill wants to merge 7 commits into
Open
NMS-19875: Topology assets: image storage for view backgrounds and custom node icons#8563marshallmassengill wants to merge 7 commits into
marshallmassengill wants to merge 7 commits into
Conversation
Custom topology views need server-side image storage for two upcoming features: background images (floor plans, rack diagrams) behind the canvas, and operator-uploaded node icons. Both are the same problem -- binary image bytes referenced by id from a view's definition document -- so one shared asset catalog serves both, distinguished by a 'kind'. The new features/topology-assets module stores the bytes in the generic binary key-value store (PostgresBlobStore, kvstore_bytea) and the catalog metadata as a JSON document in the JSON key-value store, keyed by the same generated UUID, so listings never drag image payloads along. The blob store is constructed internally from the DataSource rather than injected: the only BlobStore exposed as a shared service in the core context is thresholding's no-op, and registering a second one would make that lookup ambiguous.
Upload, serve, list, and delete topology image assets. Uploads are the raw image bytes under their own content type (no multipart), with the name and kind as query parameters; raster types only (SVG is excluded for now -- it can carry active content and these are served from the application origin), and each kind has its own size cap: icons 512 KiB, backgrounds 10 MiB. Bytes are served with an ETag derived from the asset's last-modified time plus a max-age, so icons reused across many nodes revalidate as 304s. Access is the standard /api/v2 RBAC: any authenticated user reads, ROLE_REST/ROLE_ADMIN writes.
Add the topology-assets jar and the Postgres BlobStore implementation to the base assembly -- only the no-op blob store shipped in lib until now.
TopologyAssetDaoIT runs the DAO against a real temporary database -- metadata through the PostgresJsonStore and bytes through a PostgresBlobStore, the same pairing the production wiring builds. TopologyAssetRestServiceIT drives /api/v2/topology/assets through the CXF harness: the upload/serve/delete lifecycle, byte integrity, ETag revalidation (304), kind filtering, and the validation statuses (400 missing name/kind, 413 over the per-kind cap, 415 non-image type, 404 unknown id).
…ts-rest-r36 Adapts the topology-assets module's parent reference to the 36.0.2-SNAPSHOT version bump that came with the 36.0.1 release.
Contributor
There was a problem hiding this comment.
Pull request overview
Adds server-side storage and an /api/v2 REST API for topology view image assets (backgrounds and custom node icons), storing metadata and bytes separately using the existing Postgres-backed KV stores.
Changes:
- Introduces a new
features/topology-assetsmodule providing theTopologyAssetmodel and a KV-store-backedTopologyAssetDaoimplementation. - Adds
TopologyAssetRestService(/api/v2/topology/assets) to upload/list/fetch/delete assets, including caching via ETag and per-kind size limits. - Wires the new feature into the build/assembly and adds integration tests for both DAO and REST lifecycle/validation behavior.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| opennms-webapp-rest/src/test/java/org/opennms/web/rest/v2/TopologyAssetRestServiceIT.java | CXF-based integration tests for upload/serve/list/filter/delete + validation status codes. |
| opennms-webapp-rest/src/main/java/org/opennms/web/rest/v2/TopologyAssetRestService.java | New JAX-RS v2 resource implementing the topology assets REST API. |
| opennms-webapp-rest/src/main/java/org/opennms/web/rest/v2/TopologyAssetDTO.java | DTO representing asset metadata returned by the REST API. |
| opennms-webapp-rest/pom.xml | Adds dependency on the new org.opennms.features.topology-assets module. |
| opennms-base-assembly/pom.xml | Ships the topology-assets module and Postgres BlobStore implementation in the base assembly. |
| features/topology-assets/src/test/java/org/opennms/netmgt/topology/assets/TopologyAssetDaoIT.java | DAO integration tests against a temporary database (metadata + bytes). |
| features/topology-assets/src/main/resources/META-INF/opennms/component-dao.xml | Spring/OSGi wiring exposing TopologyAssetDao. |
| features/topology-assets/src/main/java/org/opennms/netmgt/topology/assets/TopologyAssetDao.java | DAO API for storing/fetching/listing/deleting assets and bytes. |
| features/topology-assets/src/main/java/org/opennms/netmgt/topology/assets/TopologyAsset.java | Asset metadata model (id, kind, mimeType, size, owner, timestamps). |
| features/topology-assets/src/main/java/org/opennms/netmgt/topology/assets/impl/TopologyAssetKvStore.java | KV-store-backed DAO implementation using JsonStore + PostgresBlobStore. |
| features/topology-assets/pom.xml | New feature module POM and dependencies. |
| features/pom.xml | Registers the new topology-assets module in the features build. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Make null-handling in TopologyAssetKvStore.get explicit (flatMap/ofNullable) so a malformed stored document reads as absent. (Optional.map already wrapped null results, so this is clarity, not a behavior change.) - Answer 415 with a clear message when an upload carries no Content-Type header at all, instead of an NPE-driven 500. - IT: the query-string helper now presents decoded parameter values (as a servlet container would) while the query string stays encoded. - IT: assert the empty catalog by parsed JSON size rather than the raw response string.
…context The BSM and status REST ITs load the shared /api/v2 CXF context, which instantiates every v2 resource -- including the new topology-assets service. Its model jar and the jsonStore bean's module are declared provided in opennms-webapp-rest (the jars ship in $OPENNMS_HOME/lib), and provided dependencies are not transitive, so those modules' test classpaths lacked them: NoClassDefFoundError on TopologyAsset took down all 56 tests. Declare both as test-scope dependencies where the context is booted.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Custom topology views need server-side image storage for two planned
features: background images (floor plans, rack diagrams) behind the canvas,
and operator-uploaded node icons. Both reduce to the same problem — binary
image bytes referenced by id from a view's definition document — so one
shared asset catalog serves both, distinguished by a
kind(
background|icon).features/topology-assetsmodule: image bytes in the generic binarykey-value store (
PostgresBlobStore,kvstore_bytea) and catalog metadata(name, kind, MIME type, size, owner, timestamps) as a JSON document in the
JSON key-value store, keyed by the same generated UUID — listings never
drag image payloads along. No new schema. The blob store is constructed
internally from the DataSource: the only BlobStore exposed as a shared
service in the core context is thresholding's no-op, and registering a
second one would make that lookup ambiguous.
/api/v2/topology/assets(TopologyAssetRestService): POST raw imagebytes (no multipart; name/kind as query parameters), GET list with
optional
?kind=filter, GET{id}serving the bytes under the asset'sown content type with an ETag +
max-age(icons reused across many nodesrevalidate as 304s), GET
{id}/meta, DELETE. Raster types only(png/jpeg/gif/webp); SVG is excluded for now since it can carry active
content and assets are served from the application origin. Per-kind size
caps: icons 512 KiB, backgrounds 10 MiB.
/api/v2RBAC: any authenticated user reads,ROLE_REST/ROLE_ADMIN writes.
implementation (only the no-op blob store was in
libbefore).Testing:
TopologyAssetDaoIT(3) against a temporary database andTopologyAssetRestServiceIT(3) covering the upload/serve/delete lifecycle,byte integrity, ETag revalidation, kind filtering, and the validation
statuses (400/413/415/404). Also verified live (upload → byte-identical
serve → 304 → delete; anonymous → 401).
The consuming UI (background rendering and the node icon picker) ships in
the topology UI PR; this PR is the storage/API half and is independently
mergeable (the UI degrades gracefully without it).
Assisted-by: Claude Code:Opus 4.8/Fable 5
Jira: https://opennms.atlassian.net/browse/NMS-19875