Skip to content

Fixes 31887: Keep service lists available when a connection cannot decrypt - #35681

Open
Vamsi-klu wants to merge 3 commits into
open-metadata:mainfrom
Vamsi-klu:codex/fix-31887-service-list-resilience
Open

Vamsi-klu wants to merge 3 commits into
open-metadata:mainfrom
Vamsi-klu:codex/fix-31887-service-list-resilience

Conversation

@Vamsi-klu

@Vamsi-klu Vamsi-klu commented Oct 17, 2026 •

Copy link
Copy Markdown

Describe your changes:

Fixes #31887

One service with an undecryptable connection currently fails the entire list request. Handle InvalidServiceConnectionException per list entry and return that service with its connection cleared. Healthy services remain usable, and the broken service remains identifiable so its configuration can be repaired.

Suggested reviewers

@mohityadav766.

Type of change:

  • Bug fix

High-level design:

ADR:2026-10-17-service-list-connection-fallback records the deliberate list-versus-single-service error contract, the response-only fallback and its repair implications.

Use each service resource's existing nullifyConnection hook. Keep the original ResultList envelope, entry order, pagination and permissions, including replacement objects returned by the hook. Log the service category and UUID without the exception or secret-bearing details. Single-service reads, writes and unrelated errors retain their existing behavior; the fallback does not persist changes.

The tests include all service categories. The MCP placeholder becomes a real HTTP/API-key masking test, and the Overview fixture now creates the consumer principal its test client already uses. Metadata resilience cases run in isolation because that endpoint ignores domain filtering and reports a global total.

Tests:

Use cases covered

  • Healthy/broken/healthy lists, broken-only cursor pages and immutable input lists.
  • Wrong-key and malformed Fernet tokens, nested SSL-secret errors, invalid inner configuration, absent connections and null configuration.
  • All 13 service categories over HTTP, including Security and MCP.
  • Human secret masking, authorized bot credentials, per-ID permissions, requested fields and forward/backward pagination.
  • Stored encrypted JSON stays unchanged. Restoring the raw fixture restores the healthy response exactly.
  • Single-service GET-by-ID/name and invalid PUT behavior stay strict. Unrelated authorization, masking and programming errors propagate.

Unit tests

ServiceEntityResourceTest: 6 passed, zero failures/skips. The original implementation produced two intended errors from the unhandled list exception.

JaCoCo measured 56/58 lines, 96.55% in the changed production class. The changed list method and helper have 100% line coverage.

Backend integration tests

Added ServiceConnectionResilienceIT; updated McpServiceResourceIT and ServicesOverviewResourceIT.

Both engine pairs ran the same 25 selected API classes:

Profile Tests, including skips Skipped Failures Errors
MySQL / Elasticsearch 3,396 409 0 0
PostgreSQL / OpenSearch 3,396 409 0 0

All 19 new resilience invocations, the new MCP smoke test and 27 Overview cases pass on both profiles. These owned-case counts were checked by testcase classname across all XML reports because concurrent Failsafe reports can credit tests to another suite.

The MySQL run used the repository affected-test runner:

make java_affected_run ARGS='--add-area services --reason "shared service list fallback affects all service categories" --keep-going'

The PostgreSQL run used the same selection and parallel lane with -Ppostgres-opensearch. Exact Maven commands and per-class results are included below. The nightly-only ServiceDeleteSearchCleanupScaleIT was not run locally.

Local Java test run: PASSED

  • Commit: 4e57bca9ba27 (base origin/main) — uncommitted changes were present
  • Finished: 2026-10-17 11:58 UTC · 9.5 min
Step Scope Classes Tests Failed Skipped Min Result
unit openmetadata-service (1 class) 1 6 0 0 2.5 passed
integration mysql-elasticsearch · parallel 25 3396 0 409 7.0 passed
  • Impacted, not run locally: 1 class (listed in the details)

The tested files were copied byte for byte into this PR's isolated branch. The generated block records the original validation checkout rather than claiming a later commit was the test-run HEAD.

The review follow-up at a4f47a6117a209b343bb90bb9a189a1937c4cbd8 adds only the decision record and one Java comment. All four regression test files remain byte-identical to the validated commit, and removing that exact comment reproduces the validated production file byte for byte. Executable behavior is unchanged. Fresh Java 21 Spotless apply/check, decision-record validation, impact-map ownership and enabled commit hooks passed; the planner still selects the same 25 API classes. The completed unit/API results above remain applicable, so those suites were not repeated for this documentation-only follow-up.

Final spotless:apply spotless:check, impact-map ownership and git diff --check passed. No workflow, schema or impact-map change is needed.

Selected classes, report counts and commands

Local Java test run: PASSED — details

  • Commit: 4e57bca9ba27 (base origin/main) — uncommitted changes were present

Tests run locally

  • unit · openmetadata-service (1 class): ServiceEntityResourceTest (6 passed)
  • integration · mysql-elasticsearch · parallel: APIServiceResourceIT (78 passed, 15 skipped), BaseEntityIT$ListEntityHistoryByTimestampPaginationTest (2 passed, 3 skipped), ConfigResourceIT (5 passed), DashboardServiceResourceIT (58 passed, 11 skipped), DatabaseServiceResourceIT (1 passed, 1 skipped), DatabaseSmokeIT (6 passed), DriveServiceResourceIT (52 passed, 8 skipped), GlossaryTermResourceIT (154 passed, 3 skipped), LLMServiceResourceIT (27 passed, 5 skipped), McpServiceResourceIT (13 passed), MessagingServiceResourceIT (365 passed, 55 skipped), MetadataServiceResourceIT (43 passed, 7 skipped), MlModelServiceResourceIT (60 passed, 9 skipped), MlModelServiceSearchAliasIT (4 passed), PipelineServiceResourceIT (421 passed, 70 skipped), SearchServiceResourceIT (70 passed, 14 skipped), SecurityServiceResourceIT (62 passed, 9 skipped), SeedDataPresenceIT (2 passed), ServiceAttributePolicyIT (26 passed, 4 skipped), ServiceConnectionResilienceIT (19 passed), ServiceLineagePipelineRoutingMigrationIT (8 passed), ServiceLineageRefcountIT (56 passed, 6 skipped), ServicesOverviewResourceIT (70 passed, 7 skipped), StorageServiceResourceIT (294 passed, 48 skipped), TableResourceIT (166 passed, 6 skipped), UserResourceIT (335 passed, 38 skipped)

Per-class counts come from the test reports, which can credit a test to the wrong class when classes run concurrently, and keep only the last run of a nested class that several test classes inherit. The summary's step totals are Maven's own counts.

Why these tests
  • added by author: shared service list fallback affects all service categories ← area services
  • area services ← openmetadata-service/src/main/java/org/openmetadata/service/resources/services/ServiceEntityResource.java
  • called from entity APIService ← openmetadata-service/src/main/java/org/openmetadata/service/resources/services/ServiceEntityResource.java
  • called from entity DashboardService ← openmetadata-service/src/main/java/org/openmetadata/service/resources/services/ServiceEntityResource.java
  • called from entity DatabaseService ← openmetadata-service/src/main/java/org/openmetadata/service/resources/services/ServiceEntityResource.java
  • called from entity DriveService ← openmetadata-service/src/main/java/org/openmetadata/service/resources/services/ServiceEntityResource.java
  • called from entity LLMService ← openmetadata-service/src/main/java/org/openmetadata/service/resources/services/ServiceEntityResource.java
  • called from entity McpService ← openmetadata-service/src/main/java/org/openmetadata/service/resources/services/ServiceEntityResource.java
  • called from entity MessagingService ← openmetadata-service/src/main/java/org/openmetadata/service/resources/services/ServiceEntityResource.java
  • called from entity MetadataService ← openmetadata-service/src/main/java/org/openmetadata/service/resources/services/ServiceEntityResource.java
  • called from entity MlModelService ← openmetadata-service/src/main/java/org/openmetadata/service/resources/services/ServiceEntityResource.java
  • called from entity PipelineService ← openmetadata-service/src/main/java/org/openmetadata/service/resources/services/ServiceEntityResource.java
  • called from entity SearchService ← openmetadata-service/src/main/java/org/openmetadata/service/resources/services/ServiceEntityResource.java
  • called from entity SecurityService ← openmetadata-service/src/main/java/org/openmetadata/service/resources/services/ServiceEntityResource.java
  • called from entity StorageService ← openmetadata-service/src/main/java/org/openmetadata/service/resources/services/ServiceEntityResource.java
  • changed test ← openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/McpServiceResourceIT.java, openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/ServiceConnectionResilienceIT.java, openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/ServicesOverviewResourceIT.java, openmetadata-service/src/test/java/org/openmetadata/service/resources/services/ServiceEntityResourceTest.java
  • smoke ← code other than a test class changed
Impacted, not run locally (1)
  • ServiceDeleteSearchCleanupScaleIT → nightly: openmetadata-nightly k8s-java-it (scale-it)
Commands
mvn -B package -pl openmetadata-service -am -Dspring-boot.repackage.skip=true -Dtest=ServiceEntityResourceTest -Dsurefire.failIfNoSpecifiedTests=false
mvn -B verify -pl :openmetadata-integration-tests -am -Dspring-boot.repackage.skip=true -Pmysql-elasticsearch -DintegrationTests.lane=parallel -Dit.test=APIServiceResourceIT,ConfigResourceIT,DashboardServiceResourceIT,DatabaseServiceResourceIT,DatabaseSmokeIT,DriveServiceResourceIT,GlossaryTermResourceIT,LLMServiceResourceIT,McpServiceResourceIT,MessagingServiceResourceIT,MetadataServiceResourceIT,MlModelServiceResourceIT,MlModelServiceSearchAliasIT,PipelineServiceResourceIT,SearchServiceResourceIT,SecurityServiceResourceIT,SeedDataPresenceIT,ServiceAttributePolicyIT,ServiceConnectionResilienceIT,ServiceLineagePipelineRoutingMigrationIT,ServiceLineageRefcountIT,ServicesOverviewResourceIT,StorageServiceResourceIT,TableResourceIT,UserResourceIT -Dfailsafe.failIfNoSpecifiedTests=false -Dtest=NoUnitTestsInThisRun -Dsurefire.failIfNoSpecifiedTests=false

PostgreSQL/OpenSearch rerun of the same selection, 3,396 tests including 409 skips, zero failures/errors:

mvn -B verify -pl :openmetadata-integration-tests -am -Dspring-boot.repackage.skip=true -Ppostgres-opensearch -DintegrationTests.lane=parallel -Dit.test=APIServiceResourceIT,ConfigResourceIT,DashboardServiceResourceIT,DatabaseServiceResourceIT,DatabaseSmokeIT,DriveServiceResourceIT,GlossaryTermResourceIT,LLMServiceResourceIT,McpServiceResourceIT,MessagingServiceResourceIT,MetadataServiceResourceIT,MlModelServiceResourceIT,MlModelServiceSearchAliasIT,PipelineServiceResourceIT,SearchServiceResourceIT,SecurityServiceResourceIT,SeedDataPresenceIT,ServiceAttributePolicyIT,ServiceConnectionResilienceIT,ServiceLineagePipelineRoutingMigrationIT,ServiceLineageRefcountIT,ServicesOverviewResourceIT,StorageServiceResourceIT,TableResourceIT,UserResourceIT -Dfailsafe.failIfNoSpecifiedTests=false -Dtest=NoUnitTestsInThisRun -Dsurefire.failIfNoSpecifiedTests=false

Ingestion integration tests

Not applicable, no ingestion changes.

Playwright (UI) tests

Not applicable, no UI changes.

NOT NEEDED for this backend-only PR.

Manual testing performed

Scripted a standalone HTTP/storage/recovery probe against the refreshed source-built server. Created a unique domain and three password-bearing services, corrupted one test-owned stored password and confirmed the list returned HTTP 200 with all three identities. The healthy connections remained available and the broken connection was absent. Its single-service GET still returned HTTP 400. The probe verified unchanged stored JSON, restored the exact snapshot and deleted its test entities. Before the fix, the same corrupted list returned HTTP 400.

UI screen recording:

Not applicable, backend-only bug fix.

Checklist:

  • Read the contribution guide and applicable repository instructions.
  • Title follows Fixes <issue-number>: <short explanation>.
  • Linked issue through Fixes #31887.
  • Added and ran unit and real API regressions for the exact list failure.
  • Ran formatting and affected-test checks, with coverage and exclusions recorded above.
  • Comments, schema migrations and UI recording requirements checked; no schema or UI change.
  • Decision check: added docs/decisions/2026-10-17-service-list-connection-fallback.md and cited it from decryptServiceForList; lists retain broken service identities with a cleared connection, while single-service reads and writes remain strict.

RetriggerView in GreptileConfidence Score: 5/5

The PR appears safe to merge.

Summary

Service lists keep services whose connections cannot decrypt, with their connections cleared. Single-service operations remain strict.

  • Service lists keep broken services visible without their connections.

Reviews (2) · Last reviewed commit: "Fixes 31887: Use nullOrEmpty for service..." · Reviewed by Greptile

@github-actions

Copy link
Copy Markdown
Contributor

Hi there 👋 Thanks for your contribution!

The OpenMetadata team will review the PR shortly! Once it has been labeled as safe to test, the CI workflows
will start executing and we'll be able to make sure everything is working as expected.

Let us know if you need any help!

@Vamsi-klu

Copy link
Copy Markdown
Author

@mohityadav766, could you review the per-service decryption fallback? It preserves service identities and pagination when one connection cannot decrypt, while leaving stored configuration unchanged. The description includes unit and API results for both database/search profiles.

@github-actions

Copy link
Copy Markdown
Contributor

Hi there 👋 Thanks for your contribution!

The OpenMetadata team will review the PR shortly! Once it has been labeled as safe to test, the CI workflows
will start executing and we'll be able to make sure everything is working as expected.

Let us know if you need any help!

@Vamsi-klu
Vamsi-klu marked this pull request as ready for review October 17, 2026 21:30
@Vamsi-klu
Vamsi-klu requested a review from a team as a code owner October 17, 2026 21:30
@Vamsi-klu
Vamsi-klu requested a review from manerow October 17, 2026 21:30
@github-actions

Copy link
Copy Markdown
Contributor

❌ PR checklist incomplete

This PR cannot be merged until the following are addressed on its linked issue:

The fields live on the linked issue in the Shipping project (open the issue → right sidebar → Projects). After you set them, re-run this check (or push a commit) — issue/project changes do not re-trigger it automatically.

Maintainers can bypass this check by adding the skip-pr-checks label.

protected ResultList<T> decryptOrNullify(
SecurityContext securityContext, ResultList<T> services) {
listOrEmpty(services.getData()).forEach(service -> decryptOrNullify(securityContext, service));
if (services.getData() != null) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

use nullOrEmpty

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Thanks, updated this to use nullOrEmpty. The null and empty list tests and the affected backend checks pass.

@github-actions

Copy link
Copy Markdown
Contributor

Hi there 👋 Thanks for your contribution!

The OpenMetadata team will review the PR shortly! Once it has been labeled as safe to test, the CI workflows
will start executing and we'll be able to make sure everything is working as expected.

Let us know if you need any help!

@gitar-bot

gitar-bot Bot commented Oct 17, 2026

Copy link
Copy Markdown
Code Review ✅ Approved 1 closed / 1 findings

🟡 Medium risk · List endpoints now return broken services with null connections; clients could mistake undecryptable configuration for an intentionally absent connection.

Keeps service lists available when a service connection cannot be decrypt by returning the affected service with its connection cleared, while healthy services and single-service reads stay unchanged. The previously missing record of the list-versus-single-read connection fallback rule is now added to the tree. No open issues remain.

✅ 1 closed
✅ Quality: List vs. single-read connection fallback rule is not recorded in the tree

📄 openmetadata-service/src/main/java/org/openmetadata/service/resources/services/ServiceEntityResource.java:83-97
This PR changes the REST response contract for all 13 GET /v1/services/<collection> list endpoints. When a service's connection can't be decrypted, the list used to return HTTP 400. It now returns HTTP 200 with that row's connection cleared. GET /{id}, GET /name/{fqn} and PUT deliberately stay strict. Callers have to agree with this split, including the UI and any ingestion or bot client that lists services. For those callers, a null connection in a list row can now mean "undecryptable" as well as "no connection configured" or "masked for this caller".

The rule exists only in the PR description: "Handle InvalidServiceConnectionException per list entry and return that service with its connection cleared… Single-service reads, writes and unrelated errors retain their existing behavior; the fallback does not persist changes." The repo squash-merges, so that text won't reach git log. Nothing in docs/decisions/ covers it. The checklist item "no new public field, default or limit requiring an ADR" misses the trigger: a REST response shape change, stated in prose in the description and nowhere in the tree. A later change could reasonably make single reads lenient too, or make list rows fail, without knowing this split was intentional.

The record would say: "Service list endpoints return a service whose stored connection fails decryption or validation (InvalidServiceConnectionException) with connection null, log only the service type and id, and do not persist the change. Single-service GET by id/name and writes keep returning 400 for such a service. Other exceptions still fail the list." A one-line comment above decryptServiceForList citing ADR:<date>-<slug> would tie the code to the record.

Verification

Rules No rules evaluated

Functional validation Not enabled

Set up

Auto-approval Not enabled

Set up

For more verification details, comment gitar display:verbose.

Options

Display: compact → Showing compact verification results.

Comment with these commands to change the behavior for this request:

Compact
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Powered by Gitar — free for open source

This branch has not been deployed

No deployments
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.

One undecryptable service connection makes the entire service list endpoint fail (decryptOrNullify never nullifies)

2 participants