Skip to content

feat(dbaas-client): read connection properties from mounted secrets with REST fallback#115

Open
kichasov wants to merge 2 commits into
mainfrom
feat/spring-mounted-secret-source
Open

feat(dbaas-client): read connection properties from mounted secrets with REST fallback#115
kichasov wants to merge 2 commits into
mainfrom
feat/spring-mounted-secret-source

Conversation

@kichasov

Copy link
Copy Markdown
Collaborator

What

Port of the Go base-client mounted-secret provider (qubership-core-lib-go-dbaas-base-client PR #70) to the Spring dbaas-client, with REST fallback.

DatabasePool now reads a database's connection properties from a Secret mounted at the fixed path /etc/secrets/dbaas-secrets before calling dbaas over REST; on a miss it falls back to REST exactly as before. This lets services whose Secret is materialized by the dbaas-operator DatabaseSecretClaim resolve their database without a runtime dependency on dbaas-aggregator.

Design: dbaas-client-mounted-secret-source-design.md (Part I shared contract + Part II Spring).

How

All changes are in dbaas-client-base (driver base modules and starters are untouched):

  • MountedSecretSource — indexes each Secret directory by its canonical (classifier, type, role) key, reads connectionProperties.json fresh on every resolve (rotation-safe), does a throttled re-scan on a miss, and evicts an entry whose files were removed.
  • ClassifierMatcher — canonical classifier (keys sorted recursively, scope lower-cased, empty top-level namespace/tenantId omitted, empty nested objects dropped). The client canonicalizes both its own classifier and the descriptor, so they only need to agree with each other. Role is matched exact after trim (an empty role matches a descriptor whose userRole was left unset).
  • DatabasePool.createDatabase — consult the mounted source between the custom LogicalDbProvider chain and the REST call; build the typed AbstractDatabase via the synthetic-response mechanism (objectMapper.convertValue(map, type.getDatabaseClass())). Always on; when nothing is mounted the index is empty and the pool falls back to REST.
  • pom.xml — adds jackson-databind (version managed by the parent).

Backward compatibility

  • Public method/constructor signatures, the LogicalDbProvider SPI, the starters' beans and microservice code are unchanged.
  • The L2 cache key is unchanged — role participates only in the mounted-secret lookup (from DatabaseConfig.userRole), not in the cache key.
  • Runtime behaviour is identical when nothing is mounted (/etc/secrets/dbaas-secrets absent → empty index → REST). The feature is opt-in by mounting the Secret via the Deployment.

Tests

mvn -pl dbaas-client/dbaas-client-java/dbaas-client-base -am testBUILD SUCCESS, 57 tests, 0 failures.

  • MountedSecretSourceTest — hit/miss, fresh read on rotation, corrupt/missing metadata, evict on removal, exact role matching (incl. empty-matches-unset), canonical order/scope/type-case and omit-empty.
  • DatabasePoolMountedSecretTest — source-first builds the DB without calling REST; falls back to REST when nothing is mounted.

Scope / follow-ups

  • Spring only (dbaas-client-java). Quarkus (core-quarkus-extensions/dbaas-client, Part III of the design — LogicalDbProvider chain + Mongo SPI) is a separate change.
  • Per-driver marshalling parity tests (synthetic-response == REST parse for postgres/mongo/…) belong in the driver modules — base verifies it on TestDatabase/TestDBConnection.
  • Role in the cache key (as the Go provider does via NewKeyWithDiscriminator) is intentionally out of scope: the Spring discriminator slot is used by connector settings, and adding role would change REST-path caching too. The pre-existing "one classifier + multiple access roles share a cache entry" limitation is unchanged.

Operational note

The mounted lookup matches the requested role string, with no aggregator-side role resolution. DatabaseSecretClaim.spec.userRole must equal what the consumer sends as DatabaseConfig.userRole (default: both empty → match). A mismatch is a silent miss that falls back to REST — observable via the mounted-secret DEBUG hit/miss logs.

…ith REST fallback

Port of the Go base-client mounted-secret provider (qubership-core-lib-go-dbaas-base-client
PR #70) to the Spring dbaas-client. DatabasePool now reads a database's connection
properties from a Secret mounted at /etc/secrets/dbaas-secrets before calling dbaas over
REST; on a miss it falls back to REST exactly as before. This removes the runtime
dependency on dbaas-aggregator for services whose Secret is materialized by the
dbaas-operator DatabaseSecretClaim.

- MountedSecretSource: indexes each Secret directory by its canonical (classifier, type,
  role) key, reads connectionProperties.json fresh on every resolve (rotation-safe),
  throttled re-scan on a miss, and evicts an entry whose files were removed.
- ClassifierMatcher: canonical classifier (keys sorted recursively, scope lower-cased,
  empty top-level namespace/tenantId omitted, empty nested objects dropped); role matched
  exact after trim (an empty role matches a descriptor whose userRole was left unset).
- DatabasePool.createDatabase: consult the mounted source between the custom
  LogicalDbProvider chain and the REST call; build the typed AbstractDatabase via
  synthetic-response (objectMapper.convertValue(map, type.getDatabaseClass())). Always on;
  when nothing is mounted the index is empty and the pool falls back to REST.

Public API, constructors, the LogicalDbProvider SPI, the starters and the L2 cache key are
unchanged: role participates only in the mounted-secret lookup, not in the cache key.

Tests: MountedSecretSourceTest (hit/miss, fresh read on rotation, corrupt/missing metadata,
evict on removal, exact role matching incl. empty-matches-unset, canonical order/scope/type
case and omit-empty); DatabasePoolMountedSecretTest (source-first builds the DB without
calling REST; falls back to REST when nothing is mounted).
@kichasov kichasov requested a review from lis0x90 as a code owner June 22, 2026 10:19
@github-actions github-actions Bot added the enhancement New feature or request label Jun 22, 2026
…x field

Address the SonarCloud quality gate on the mounted-secret feature (coverage on new code
73.4% < 80%, Reliability B):

- Reliability: replace the volatile mutable Map index in MountedSecretSource with an
  AtomicReference (S3077). The reference was only ever swapped for an immutable map, but the
  volatile-mutable-type pattern dropped the rating to B.
- Coverage: add tests for corrupt/incomplete metadata and connectionProperties, duplicate
  keys, non-directory entries, and the throttled re-scan on a miss (via a new package-private
  constructor seam that takes the rescan throttle); a ClassifierMatcherTest for the canonical
  edge cases (null type/role, null values, empty/non-empty nested objects, scalars); and a
  DatabasePool case exercising the synthetic-response fallbacks (name/namespace/settings).
- Minor smells: drop an unused test parameter; suppress the fixed-mount-path S1075 and the
  deprecated DatabaseDefinitionHandler stub (DatabasePool's only constructors require it).
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants