geyanggang opened a new pull request, #13769:
URL: https://github.com/apache/gravitino/pull/13769

   ### What changes were proposed in this pull request?
   
   Relational JDBC catalog creation now verifies the backend is reachable, 
consistently
   across JDBC providers, instead of silently succeeding and failing later on 
an unrelated
   operation. The check is bounded so it fails fast and reports the failure to 
the caller
   naming the host.
   
   Three coordinated changes:
   
   1. **Bounded JDBC connect/socket timeout (`catalog-jdbc-common`).**
      - Add `jdbc.pool.connect-timeout-ms` to `JdbcConfig` (default `30000`, 
positive-value
        check), plus a `getConnectTimeoutMs()` getter.
      - Add a pure, driver-aware utility `JdbcConnectionTimeouts` that 
translates this
        canonical milliseconds value into each driver family's connection 
parameters, since
        the parameter unit differs: MySQL Connector/J 
(`com.mysql.cj.jdbc.Driver` /
        legacy `com.mysql.jdbc.Driver`, also used by Doris/StarRocks) takes 
milliseconds;
        PostgreSQL (`org.postgresql.Driver`) takes seconds (ms rounded up, 
minimum 1). An
        unknown driver family yields no properties, so adding a new provider 
never breaks.
        Any value the operator already set — as a connection property or a 
`jdbc-url` query
        parameter — is preserved (defaults, never overrides; the URL check is 
case-sensitive
        and boundary-anchored).
      - `DataSourceUtils.createDBCPDataSource` applies the translated 
properties via
        `addConnectionProperty`, so they are per-connection bounds, do not 
collide with the
        unsafe-pool-property guard, and keep the pool lazy.
   
   2. **`gravitino.catalog.verify-on-create` control property (`core`).**
      Registered in `BaseCatalogPropertiesMetadata` as a hidden, non-reserved 
boolean with
      default `true`, with the key constant on `Catalog`. It is a registered 
property (so it
      is not silently dropped during create validation) that lets an 
administrator opt out of
      create-time verification to register a catalog whose backend is 
temporarily unavailable.
   
   3. **Create-time verification via a `validateOnCreate()` hook (`core` + 
`jdbc`).**
      - Add `BaseCatalog.validateOnCreate()`, whose default body eagerly 
initializes `ops()`,
        preserving current behavior for existing opt-ins (e.g. Iceberg REST).
      - `CatalogManager.initCatalogWrapper` now calls `validateOnCreate()` 
instead of `ops()`
        on the existing `shouldValidateOnCreate()` opt-in path (one-line 
change).
      - `JdbcCatalog` overrides `shouldValidateOnCreate()` (reads 
`verify-on-create`,
        defaulting to `true`) and `validateOnCreate()` to run a single 
`testConnection`
        (one `listDatabases` round trip). On failure a 
`ConnectionFailedException` propagates,
        and the existing create rollback removes the just-persisted catalog 
entity.
      - `JdbcCatalogOperations.testConnection` enriches the 
`ConnectionFailedException`
        message with the `host:port` parsed from the `jdbc-url` (same exception 
type).
   
   Docs: `docs/jdbc-catalog-connection-validation.md` documents the create-time 
verification,
   the override, and the bounded connect timeout.
   
   ### Why are the changes needed?
   
   Creating a relational JDBC catalog returned success without any check that 
the backend was
   reachable, and the behavior was not consistent across providers. An 
administrator who
   mistyped a host, port, or credential got a success response and a catalog 
that did not
   work; the failure then surfaced later on an unrelated operation.
   
   The cause is that `CatalogManager.createCatalog` performed no connection 
check — whether
   the backend was touched during create depended entirely on each connector's 
`initialize()`
   side effects. JDBC catalogs build a lazy DBCP2 connection pool
   (`DataSourceUtils.createDBCPDataSource`, with `initialSize` blocked), so 
create opened no
   connection and never reached the backend. The existing 
`BaseCatalog.shouldValidateOnCreate()`
   hook only eagerly called `ops()`, which for JDBC still opened no connection, 
so even
   opting in did not verify reachability.
   
   There was also no bounded JDBC connect/socket timeout: `JdbcConfig` exposed 
only pool
   settings and `DataSourceUtils` set only the pool `maxWait`, so a connection 
attempt against
   an unreachable host could block for the driver default (effectively 
unbounded for the
   MySQL-family default `connectTimeout=0`). Bounding it is a prerequisite for 
safe create-time
   verification (which runs inside the metalake write lock) and a useful 
hardening on its own.
   
   Fix: #13767 
   
   ### Does this PR introduce _any_ user-facing change?
   
   Yes.
   
   - **Behavior:** By default, creating a relational JDBC catalog now verifies 
the backend is
     reachable and returns a connection-failed error (naming the host) if it is 
not, instead of
     succeeding. Set `gravitino.catalog.verify-on-create=false` on the create 
request to restore
     the previous unverified behavior.
   - **New property keys:**
     - `gravitino.catalog.verify-on-create` (catalog property, boolean, default 
`true`, hidden).
     - `jdbc.pool.connect-timeout-ms` (JDBC catalog config, long, default 
`30000`).
   - **Unaffected:** Iceberg REST keeps its existing selective create-time 
validation; model
     and generic catalogs, and any catalog that does not opt in, are never 
force-verified. The
     standalone `testConnection` API is unchanged.
   
   ### How was this patch tested?
   
   New and extended unit tests:
   
   - `catalog-jdbc-common`:
     - `TestJdbcConnectionTimeouts` — MySQL family emits milliseconds; 
PostgreSQL converts
       ms→seconds (round up, minimum 1); unknown driver emits nothing; an 
operator-set
       `connectTimeout`/`socketTimeout` is preserved whether set as a config 
key or a `jdbc-url`
       query parameter (case-sensitive, boundary-anchored); custom-value 
propagation; null-URL
       safe.
     - `TestJdbcConfig` — the new setting's default and override.
     - `TestDataSourceValidation` — an unknown driver injects nothing and the 
pool still borrows
       a connection.
   - `core`:
     - `TestCatalogManager` — an opted-in catalog whose backend probe fails 
propagates
       `ConnectionFailedException` and rolls back the persisted entity (store 
no longer contains
       it, identifier is reusable); a non-opted-in catalog is not verified; the 
existing backend
       validation test is unchanged.
     - `TestCatalog` / `TestCatalogOperations` — simulate a failing 
`testConnection` without a
       real backend.
   
   Commands run (from the repo root):
   
   - `./gradlew spotlessApply` — SUCCESS.
   - `./gradlew :catalogs:catalog-jdbc-common:test :core:test -PskipITs` — 
BUILD SUCCESSFUL
     (core: 2699 tests, 1 skipped, 0 failed), modules compiled with checkstyle 
`-Werror`.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to